This is page 33 of 144. Use http://codebase.md/xmlui-org/xmlui/tools/vscode/resources/assets/img/%7B$item.flickr_images[0]%7D?lines=false&page={x} to view the full context.
# Directory Structure
```
├── .changeset
│ └── config.json
├── .eslintrc.cjs
├── .github
│ ├── build-checklist.png
│ ├── ISSUE_TEMPLATE
│ │ ├── bug_report.md
│ │ └── feature_request.md
│ └── workflows
│ ├── deploy-blog-optimized.yml
│ ├── deploy-blog-swa.yml
│ ├── deploy-blog.yml
│ ├── deploy-docs-optimized.yml
│ ├── deploy-docs-swa.yml
│ ├── deploy-docs.yml
│ ├── prepare-versions.yml
│ ├── release-packages.yml
│ ├── run-all-tests.yml
│ └── run-smoke-tests.yml
├── .gitignore
├── .prettierrc.js
├── .vscode
│ ├── launch.json
│ └── settings.json
├── blog
│ ├── .gitignore
│ ├── .gitkeep
│ ├── CHANGELOG.md
│ ├── extensions.ts
│ ├── index.html
│ ├── index.ts
│ ├── package.json
│ ├── public
│ │ ├── blog
│ │ │ ├── images
│ │ │ │ ├── an-advanced-codefence.gif
│ │ │ │ ├── an-advanced-codefence.mp4
│ │ │ │ ├── blog-page-component.png
│ │ │ │ ├── blog-scrabble.png
│ │ │ │ ├── codefence-runner.png
│ │ │ │ ├── integrated-blog-search.png
│ │ │ │ ├── lorem-ipsum.png
│ │ │ │ ├── playground-checkbox-source.png
│ │ │ │ ├── playground.png
│ │ │ │ ├── use-xmlui-mcp-to-find-a-howto.png
│ │ │ │ └── xmlui-demo-gallery.png
│ │ │ ├── introducing-xmlui.md
│ │ │ ├── lorem-ipsum.md
│ │ │ ├── newest-post.md
│ │ │ ├── older-post.md
│ │ │ ├── xmlui-playground.md
│ │ │ └── xmlui-powered-blog.md
│ │ ├── mockServiceWorker.js
│ │ ├── resources
│ │ │ ├── favicon.ico
│ │ │ ├── files
│ │ │ │ └── for-download
│ │ │ │ └── xmlui
│ │ │ │ └── xmlui-standalone.umd.js
│ │ │ ├── github.svg
│ │ │ ├── llms.txt
│ │ │ ├── logo-dark.svg
│ │ │ ├── logo.svg
│ │ │ ├── pg-popout.svg
│ │ │ ├── rss.svg
│ │ │ └── xmlui-logo.svg
│ │ ├── serve.json
│ │ ├── staticwebapp.config.json
│ │ └── web.config
│ ├── scripts
│ │ ├── download-latest-xmlui.js
│ │ ├── generate-rss.js
│ │ ├── get-releases.js
│ │ └── utils.js
│ ├── src
│ │ ├── components
│ │ │ ├── BlogOverview.xmlui
│ │ │ ├── BlogPage.xmlui
│ │ │ └── PageNotFound.xmlui
│ │ ├── config.ts
│ │ ├── Main.xmlui
│ │ └── themes
│ │ └── blog-theme.ts
│ └── tsconfig.json
├── CONTRIBUTING.md
├── docs
│ ├── .gitignore
│ ├── CHANGELOG.md
│ ├── ComponentRefLinks.txt
│ ├── content
│ │ ├── _meta.json
│ │ ├── components
│ │ │ ├── _meta.json
│ │ │ ├── _overview.md
│ │ │ ├── APICall.md
│ │ │ ├── App.md
│ │ │ ├── AppHeader.md
│ │ │ ├── AppState.md
│ │ │ ├── AutoComplete.md
│ │ │ ├── Avatar.md
│ │ │ ├── Backdrop.md
│ │ │ ├── Badge.md
│ │ │ ├── BarChart.md
│ │ │ ├── Bookmark.md
│ │ │ ├── Breakout.md
│ │ │ ├── Button.md
│ │ │ ├── Card.md
│ │ │ ├── Carousel.md
│ │ │ ├── ChangeListener.md
│ │ │ ├── Checkbox.md
│ │ │ ├── CHStack.md
│ │ │ ├── ColorPicker.md
│ │ │ ├── Column.md
│ │ │ ├── ContentSeparator.md
│ │ │ ├── CVStack.md
│ │ │ ├── DataSource.md
│ │ │ ├── DateInput.md
│ │ │ ├── DatePicker.md
│ │ │ ├── DonutChart.md
│ │ │ ├── DropdownMenu.md
│ │ │ ├── EmojiSelector.md
│ │ │ ├── ExpandableItem.md
│ │ │ ├── FileInput.md
│ │ │ ├── FileUploadDropZone.md
│ │ │ ├── FlowLayout.md
│ │ │ ├── Footer.md
│ │ │ ├── Form.md
│ │ │ ├── FormItem.md
│ │ │ ├── FormSection.md
│ │ │ ├── Fragment.md
│ │ │ ├── H1.md
│ │ │ ├── H2.md
│ │ │ ├── H3.md
│ │ │ ├── H4.md
│ │ │ ├── H5.md
│ │ │ ├── H6.md
│ │ │ ├── Heading.md
│ │ │ ├── HSplitter.md
│ │ │ ├── HStack.md
│ │ │ ├── Icon.md
│ │ │ ├── IFrame.md
│ │ │ ├── Image.md
│ │ │ ├── Items.md
│ │ │ ├── LabelList.md
│ │ │ ├── Legend.md
│ │ │ ├── LineChart.md
│ │ │ ├── Link.md
│ │ │ ├── List.md
│ │ │ ├── Logo.md
│ │ │ ├── Markdown.md
│ │ │ ├── MenuItem.md
│ │ │ ├── MenuSeparator.md
│ │ │ ├── ModalDialog.md
│ │ │ ├── NavGroup.md
│ │ │ ├── NavLink.md
│ │ │ ├── NavPanel.md
│ │ │ ├── NoResult.md
│ │ │ ├── NumberBox.md
│ │ │ ├── Option.md
│ │ │ ├── Page.md
│ │ │ ├── PageMetaTitle.md
│ │ │ ├── Pages.md
│ │ │ ├── Pagination.md
│ │ │ ├── PasswordInput.md
│ │ │ ├── PieChart.md
│ │ │ ├── ProgressBar.md
│ │ │ ├── Queue.md
│ │ │ ├── RadioGroup.md
│ │ │ ├── RealTimeAdapter.md
│ │ │ ├── Redirect.md
│ │ │ ├── Select.md
│ │ │ ├── Slider.md
│ │ │ ├── Slot.md
│ │ │ ├── SpaceFiller.md
│ │ │ ├── Spinner.md
│ │ │ ├── Splitter.md
│ │ │ ├── Stack.md
│ │ │ ├── StickyBox.md
│ │ │ ├── SubMenuItem.md
│ │ │ ├── Switch.md
│ │ │ ├── TabItem.md
│ │ │ ├── Table.md
│ │ │ ├── TableOfContents.md
│ │ │ ├── Tabs.md
│ │ │ ├── Text.md
│ │ │ ├── TextArea.md
│ │ │ ├── TextBox.md
│ │ │ ├── Theme.md
│ │ │ ├── TimeInput.md
│ │ │ ├── Timer.md
│ │ │ ├── ToneChangerButton.md
│ │ │ ├── ToneSwitch.md
│ │ │ ├── Tooltip.md
│ │ │ ├── Tree.md
│ │ │ ├── VSplitter.md
│ │ │ ├── VStack.md
│ │ │ ├── xmlui-animations
│ │ │ │ ├── _meta.json
│ │ │ │ ├── _overview.md
│ │ │ │ ├── Animation.md
│ │ │ │ ├── FadeAnimation.md
│ │ │ │ ├── FadeInAnimation.md
│ │ │ │ ├── FadeOutAnimation.md
│ │ │ │ ├── ScaleAnimation.md
│ │ │ │ └── SlideInAnimation.md
│ │ │ ├── xmlui-pdf
│ │ │ │ ├── _meta.json
│ │ │ │ ├── _overview.md
│ │ │ │ └── Pdf.md
│ │ │ ├── xmlui-spreadsheet
│ │ │ │ ├── _meta.json
│ │ │ │ ├── _overview.md
│ │ │ │ └── Spreadsheet.md
│ │ │ └── xmlui-website-blocks
│ │ │ ├── _meta.json
│ │ │ ├── _overview.md
│ │ │ ├── Carousel.md
│ │ │ ├── HelloMd.md
│ │ │ ├── HeroSection.md
│ │ │ └── ScrollToTop.md
│ │ └── extensions
│ │ ├── _meta.json
│ │ ├── xmlui-animations
│ │ │ ├── _meta.json
│ │ │ ├── _overview.md
│ │ │ ├── Animation.md
│ │ │ ├── FadeAnimation.md
│ │ │ ├── FadeInAnimation.md
│ │ │ ├── FadeOutAnimation.md
│ │ │ ├── ScaleAnimation.md
│ │ │ └── SlideInAnimation.md
│ │ └── xmlui-website-blocks
│ │ ├── _meta.json
│ │ ├── _overview.md
│ │ ├── Carousel.md
│ │ ├── HelloMd.md
│ │ ├── HeroSection.md
│ │ └── ScrollToTop.md
│ ├── extensions.ts
│ ├── index.html
│ ├── index.ts
│ ├── package.json
│ ├── public
│ │ ├── feed.rss
│ │ ├── mockServiceWorker.js
│ │ ├── pages
│ │ │ ├── _meta.json
│ │ │ ├── app-structure.md
│ │ │ ├── build-editor-component.md
│ │ │ ├── build-hello-world-component.md
│ │ │ ├── components-intro.md
│ │ │ ├── context-variables.md
│ │ │ ├── forms.md
│ │ │ ├── globals.md
│ │ │ ├── glossary.md
│ │ │ ├── helper-tags.md
│ │ │ ├── hosted-deployment.md
│ │ │ ├── howto
│ │ │ │ ├── assign-a-complex-json-literal-to-a-component-variable.md
│ │ │ │ ├── chain-a-refetch.md
│ │ │ │ ├── control-cache-invalidation.md
│ │ │ │ ├── debounce-user-input-for-api-calls.md
│ │ │ │ ├── debounce-with-changelistener.md
│ │ │ │ ├── debug-a-component.md
│ │ │ │ ├── delay-a-datasource-until-another-datasource-is-ready.md
│ │ │ │ ├── delegate-a-method.md
│ │ │ │ ├── do-custom-form-validation.md
│ │ │ │ ├── expose-a-method-from-a-component.md
│ │ │ │ ├── filter-and-transform-data-from-an-api.md
│ │ │ │ ├── group-items-in-list-by-a-property.md
│ │ │ │ ├── handle-background-operations.md
│ │ │ │ ├── hide-an-element-until-its-datasource-is-ready.md
│ │ │ │ ├── make-a-set-of-equal-width-cards.md
│ │ │ │ ├── make-a-table-responsive.md
│ │ │ │ ├── make-navpanel-width-responsive.md
│ │ │ │ ├── modify-a-value-reported-in-a-column.md
│ │ │ │ ├── paginate-a-list.md
│ │ │ │ ├── pass-data-to-a-modal-dialog.md
│ │ │ │ ├── react-to-button-click-not-keystrokes.md
│ │ │ │ ├── set-the-initial-value-of-a-select-from-fetched-data.md
│ │ │ │ ├── share-a-modaldialog-across-components.md
│ │ │ │ ├── sync-selections-between-table-and-list-views.md
│ │ │ │ ├── update-ui-optimistically.md
│ │ │ │ ├── use-built-in-form-validation.md
│ │ │ │ └── use-the-same-modaldialog-to-add-or-edit.md
│ │ │ ├── howto.md
│ │ │ ├── intro.md
│ │ │ ├── layout.md
│ │ │ ├── markup.md
│ │ │ ├── mcp.md
│ │ │ ├── modal-dialogs.md
│ │ │ ├── news-and-reviews.md
│ │ │ ├── reactive-intro.md
│ │ │ ├── refactoring.md
│ │ │ ├── routing-and-links.md
│ │ │ ├── samples
│ │ │ │ ├── color-palette.xmlui
│ │ │ │ ├── color-values.xmlui
│ │ │ │ ├── shadow-sizes.xmlui
│ │ │ │ ├── spacing-sizes.xmlui
│ │ │ │ ├── swatch.xmlui
│ │ │ │ ├── theme-gallery-brief.xmlui
│ │ │ │ └── theme-gallery.xmlui
│ │ │ ├── scoping.md
│ │ │ ├── scripting.md
│ │ │ ├── styles-and-themes
│ │ │ │ ├── common-units.md
│ │ │ │ ├── layout-props.md
│ │ │ │ ├── theme-variable-defaults.md
│ │ │ │ ├── theme-variables.md
│ │ │ │ └── themes.md
│ │ │ ├── template-properties.md
│ │ │ ├── test.md
│ │ │ ├── tutorial-01.md
│ │ │ ├── tutorial-02.md
│ │ │ ├── tutorial-03.md
│ │ │ ├── tutorial-04.md
│ │ │ ├── tutorial-05.md
│ │ │ ├── tutorial-06.md
│ │ │ ├── tutorial-07.md
│ │ │ ├── tutorial-08.md
│ │ │ ├── tutorial-09.md
│ │ │ ├── tutorial-10.md
│ │ │ ├── tutorial-11.md
│ │ │ ├── tutorial-12.md
│ │ │ ├── universal-properties.md
│ │ │ ├── user-defined-components.md
│ │ │ ├── vscode.md
│ │ │ ├── working-with-markdown.md
│ │ │ ├── working-with-text.md
│ │ │ ├── xmlui-animations
│ │ │ │ ├── _meta.json
│ │ │ │ ├── _overview.md
│ │ │ │ ├── Animation.md
│ │ │ │ ├── FadeAnimation.md
│ │ │ │ ├── FadeInAnimation.md
│ │ │ │ ├── FadeOutAnimation.md
│ │ │ │ ├── ScaleAnimation.md
│ │ │ │ └── SlideInAnimation.md
│ │ │ ├── xmlui-charts
│ │ │ │ ├── _meta.json
│ │ │ │ ├── _overview.md
│ │ │ │ ├── BarChart.md
│ │ │ │ ├── DonutChart.md
│ │ │ │ ├── LabelList.md
│ │ │ │ ├── Legend.md
│ │ │ │ ├── LineChart.md
│ │ │ │ └── PieChart.md
│ │ │ ├── xmlui-pdf
│ │ │ │ ├── _meta.json
│ │ │ │ ├── _overview.md
│ │ │ │ └── Pdf.md
│ │ │ └── xmlui-spreadsheet
│ │ │ ├── _meta.json
│ │ │ ├── _overview.md
│ │ │ └── Spreadsheet.md
│ │ ├── resources
│ │ │ ├── devdocs
│ │ │ │ ├── debug-proxy-object-2.png
│ │ │ │ ├── debug-proxy-object.png
│ │ │ │ ├── table_editor_01.png
│ │ │ │ ├── table_editor_02.png
│ │ │ │ ├── table_editor_03.png
│ │ │ │ ├── table_editor_04.png
│ │ │ │ ├── table_editor_05.png
│ │ │ │ ├── table_editor_06.png
│ │ │ │ ├── table_editor_07.png
│ │ │ │ ├── table_editor_08.png
│ │ │ │ ├── table_editor_09.png
│ │ │ │ ├── table_editor_10.png
│ │ │ │ ├── table_editor_11.png
│ │ │ │ ├── table-editor-01.png
│ │ │ │ ├── table-editor-02.png
│ │ │ │ ├── table-editor-03.png
│ │ │ │ ├── table-editor-04.png
│ │ │ │ ├── table-editor-06.png
│ │ │ │ ├── table-editor-07.png
│ │ │ │ ├── table-editor-08.png
│ │ │ │ ├── table-editor-09.png
│ │ │ │ └── xmlui-rendering-of-tiptap-markdown.png
│ │ │ ├── favicon.ico
│ │ │ ├── files
│ │ │ │ ├── clients.json
│ │ │ │ ├── daily-revenue.json
│ │ │ │ ├── dashboard-stats.json
│ │ │ │ ├── demo.xmlui
│ │ │ │ ├── demo.xmlui.xs
│ │ │ │ ├── downloads
│ │ │ │ │ └── downloads.json
│ │ │ │ ├── for-download
│ │ │ │ │ ├── index-with-api.html
│ │ │ │ │ ├── index.html
│ │ │ │ │ ├── mockApi.js
│ │ │ │ │ ├── start-darwin.sh
│ │ │ │ │ ├── start-linux.sh
│ │ │ │ │ ├── start.bat
│ │ │ │ │ └── xmlui
│ │ │ │ │ └── xmlui-standalone.umd.js
│ │ │ │ ├── getting-started
│ │ │ │ │ ├── cl-tutorial-final.zip
│ │ │ │ │ ├── cl-tutorial.zip
│ │ │ │ │ ├── cl-tutorial2.zip
│ │ │ │ │ ├── cl-tutorial3.zip
│ │ │ │ │ ├── cl-tutorial4.zip
│ │ │ │ │ ├── cl-tutorial5.zip
│ │ │ │ │ ├── cl-tutorial6.zip
│ │ │ │ │ ├── getting-started.zip
│ │ │ │ │ ├── hello-xmlui.zip
│ │ │ │ │ ├── xmlui-empty.zip
│ │ │ │ │ └── xmlui-starter.zip
│ │ │ │ ├── howto
│ │ │ │ │ └── component-icons
│ │ │ │ │ └── up-arrow.svg
│ │ │ │ ├── invoices.json
│ │ │ │ ├── monthly-status.json
│ │ │ │ ├── news-and-reviews.json
│ │ │ │ ├── products.json
│ │ │ │ ├── releases.json
│ │ │ │ ├── tutorials
│ │ │ │ │ ├── datasource
│ │ │ │ │ │ └── api.ts
│ │ │ │ │ └── p2do
│ │ │ │ │ ├── api.ts
│ │ │ │ │ └── todo-logo.svg
│ │ │ │ └── xmlui.json
│ │ │ ├── github.svg
│ │ │ ├── images
│ │ │ │ ├── apiaction-tutorial
│ │ │ │ │ ├── add-success.png
│ │ │ │ │ ├── apiaction-param.png
│ │ │ │ │ ├── change-completed.png
│ │ │ │ │ ├── change-in-progress.png
│ │ │ │ │ ├── confirm-delete.png
│ │ │ │ │ ├── data-error.png
│ │ │ │ │ ├── data-progress.png
│ │ │ │ │ ├── data-success.png
│ │ │ │ │ ├── display-1.png
│ │ │ │ │ ├── item-deleted.png
│ │ │ │ │ ├── item-updated.png
│ │ │ │ │ ├── missing-api-key.png
│ │ │ │ │ ├── new-item-added.png
│ │ │ │ │ └── test-message.png
│ │ │ │ ├── chat-api
│ │ │ │ │ └── domain-model.svg
│ │ │ │ ├── components
│ │ │ │ │ ├── image
│ │ │ │ │ │ └── breakfast.jpg
│ │ │ │ │ ├── markdown
│ │ │ │ │ │ └── colors.png
│ │ │ │ │ └── modal
│ │ │ │ │ ├── deep_link_dialog_1.jpg
│ │ │ │ │ └── deep_link_dialog_2.jpg
│ │ │ │ ├── create-apps
│ │ │ │ │ ├── collapsed-vertical.png
│ │ │ │ │ ├── using-forms-warning-dialog.png
│ │ │ │ │ └── using-forms.png
│ │ │ │ ├── datasource-tutorial
│ │ │ │ │ ├── data-with-header.png
│ │ │ │ │ ├── filtered-data.png
│ │ │ │ │ ├── filtered-items.png
│ │ │ │ │ ├── initial-page-items.png
│ │ │ │ │ ├── list-items.png
│ │ │ │ │ ├── next-page-items.png
│ │ │ │ │ ├── no-data.png
│ │ │ │ │ ├── pagination-1.jpg
│ │ │ │ │ ├── pagination-1.png
│ │ │ │ │ ├── polling-1.png
│ │ │ │ │ ├── refetch-data.png
│ │ │ │ │ ├── slow-loading.png
│ │ │ │ │ ├── test-message.png
│ │ │ │ │ ├── Thumbs.db
│ │ │ │ │ ├── unconventional-data.png
│ │ │ │ │ └── unfiltered-items.png
│ │ │ │ ├── flower.jpg
│ │ │ │ ├── get-started
│ │ │ │ │ ├── add-new-contact.png
│ │ │ │ │ ├── app-modified.png
│ │ │ │ │ ├── app-start.png
│ │ │ │ │ ├── app-with-boxes.png
│ │ │ │ │ ├── app-with-toast.png
│ │ │ │ │ ├── boilerplate-structure.png
│ │ │ │ │ ├── cl-initial.png
│ │ │ │ │ ├── cl-start.png
│ │ │ │ │ ├── contact-counts.png
│ │ │ │ │ ├── contact-dialog-title.png
│ │ │ │ │ ├── contact-dialog.png
│ │ │ │ │ ├── contact-menus.png
│ │ │ │ │ ├── contact-predicates.png
│ │ │ │ │ ├── context-menu.png
│ │ │ │ │ ├── dashboard-numbers.png
│ │ │ │ │ ├── default-contact-list.png
│ │ │ │ │ ├── delete-contact.png
│ │ │ │ │ ├── delete-task.png
│ │ │ │ │ ├── detailed-template.png
│ │ │ │ │ ├── edit-contact-details.png
│ │ │ │ │ ├── edited-contact-saved.png
│ │ │ │ │ ├── empty-sections.png
│ │ │ │ │ ├── filter-completed.png
│ │ │ │ │ ├── fullwidth-desktop.png
│ │ │ │ │ ├── fullwidth-mobile.png
│ │ │ │ │ ├── initial-table.png
│ │ │ │ │ ├── items-and-badges.png
│ │ │ │ │ ├── loading-message.png
│ │ │ │ │ ├── new-contact-button.png
│ │ │ │ │ ├── new-contact-saved.png
│ │ │ │ │ ├── no-empty-sections.png
│ │ │ │ │ ├── personal-todo-initial.png
│ │ │ │ │ ├── piechart.png
│ │ │ │ │ ├── review-today.png
│ │ │ │ │ ├── rudimentary-dashboard.png
│ │ │ │ │ ├── section-collapsed.png
│ │ │ │ │ ├── sectioned-items.png
│ │ │ │ │ ├── sections-ordered.png
│ │ │ │ │ ├── spacex-list-with-links.png
│ │ │ │ │ ├── spacex-list.png
│ │ │ │ │ ├── start-personal-todo-1.png
│ │ │ │ │ ├── submit-new-contact.png
│ │ │ │ │ ├── submit-new-task.png
│ │ │ │ │ ├── syntax-highlighting.png
│ │ │ │ │ ├── table-with-badge.png
│ │ │ │ │ ├── template-with-card.png
│ │ │ │ │ ├── test-emulated-api.png
│ │ │ │ │ ├── Thumbs.db
│ │ │ │ │ ├── todo-logo.png
│ │ │ │ │ └── xmlui-tools.png
│ │ │ │ ├── HelloApp.png
│ │ │ │ ├── HelloApp2.png
│ │ │ │ ├── logos
│ │ │ │ │ ├── xmlui1.svg
│ │ │ │ │ ├── xmlui2.svg
│ │ │ │ │ ├── xmlui3.svg
│ │ │ │ │ ├── xmlui4.svg
│ │ │ │ │ ├── xmlui5.svg
│ │ │ │ │ ├── xmlui6.svg
│ │ │ │ │ └── xmlui7.svg
│ │ │ │ ├── pdf
│ │ │ │ │ └── dummy-pdf.jpg
│ │ │ │ ├── rendering-engine
│ │ │ │ │ ├── AppEngine-flow.svg
│ │ │ │ │ ├── Component.svg
│ │ │ │ │ ├── CompoundComponent.svg
│ │ │ │ │ ├── RootComponent.svg
│ │ │ │ │ └── tree-with-containers.svg
│ │ │ │ ├── reviewers-guide
│ │ │ │ │ ├── AppEngine-flow.svg
│ │ │ │ │ └── incbutton-in-action.png
│ │ │ │ ├── tools
│ │ │ │ │ └── boilerplate-structure.png
│ │ │ │ ├── try.svg
│ │ │ │ ├── tutorial
│ │ │ │ │ ├── app-chat-history.png
│ │ │ │ │ ├── app-content-placeholder.png
│ │ │ │ │ ├── app-header-and-content.png
│ │ │ │ │ ├── app-links-channel-selected.png
│ │ │ │ │ ├── app-links-click.png
│ │ │ │ │ ├── app-navigation.png
│ │ │ │ │ ├── finished-ex01.png
│ │ │ │ │ ├── finished-ex02.png
│ │ │ │ │ ├── hello.png
│ │ │ │ │ ├── splash-screen-advanced.png
│ │ │ │ │ ├── splash-screen-after-click.png
│ │ │ │ │ ├── splash-screen-centered.png
│ │ │ │ │ ├── splash-screen-events.png
│ │ │ │ │ ├── splash-screen-expression.png
│ │ │ │ │ ├── splash-screen-reuse-after.png
│ │ │ │ │ ├── splash-screen-reuse-before.png
│ │ │ │ │ └── splash-screen.png
│ │ │ │ └── tutorial-01.png
│ │ │ ├── llms.txt
│ │ │ ├── logo-dark.svg
│ │ │ ├── logo.svg
│ │ │ ├── pg-popout.svg
│ │ │ └── xmlui-logo.svg
│ │ ├── serve.json
│ │ └── web.config
│ ├── scripts
│ │ ├── download-latest-xmlui.js
│ │ ├── generate-rss.js
│ │ ├── get-releases.js
│ │ └── utils.js
│ ├── src
│ │ ├── components
│ │ │ ├── BlogOverview.xmlui
│ │ │ ├── BlogPage.xmlui
│ │ │ ├── Boxes.xmlui
│ │ │ ├── Breadcrumb.xmlui
│ │ │ ├── ChangeLog.xmlui
│ │ │ ├── ColorPalette.xmlui
│ │ │ ├── DocumentLinks.xmlui
│ │ │ ├── DocumentPage.xmlui
│ │ │ ├── DocumentPageNoTOC.xmlui
│ │ │ ├── Icons.xmlui
│ │ │ ├── IncButton.xmlui
│ │ │ ├── IncButton2.xmlui
│ │ │ ├── NameValue.xmlui
│ │ │ ├── PageNotFound.xmlui
│ │ │ ├── PaletteItem.xmlui
│ │ │ ├── Palettes.xmlui
│ │ │ ├── SectionHeader.xmlui
│ │ │ ├── TBD.xmlui
│ │ │ ├── Test.xmlui
│ │ │ ├── ThemesIntro.xmlui
│ │ │ ├── ThousandThemes.xmlui
│ │ │ ├── TubeStops.xmlui
│ │ │ ├── TubeStops.xmlui.xs
│ │ │ └── TwoColumnCode.xmlui
│ │ ├── config.ts
│ │ ├── Main.xmlui
│ │ └── themes
│ │ ├── docs-theme.ts
│ │ ├── earthtone.ts
│ │ ├── xmlui-gray-on-default.ts
│ │ ├── xmlui-green-on-default.ts
│ │ └── xmlui-orange-on-default.ts
│ └── tsconfig.json
├── LICENSE
├── package-lock.json
├── package.json
├── packages
│ ├── tsconfig.json
│ ├── xmlui-animations
│ │ ├── .gitignore
│ │ ├── CHANGELOG.md
│ │ ├── demo
│ │ │ └── Main.xmlui
│ │ ├── index.html
│ │ ├── index.ts
│ │ ├── meta
│ │ │ └── componentsMetadata.ts
│ │ ├── package.json
│ │ └── src
│ │ ├── Animation.tsx
│ │ ├── AnimationNative.tsx
│ │ ├── FadeAnimation.tsx
│ │ ├── FadeInAnimation.tsx
│ │ ├── FadeOutAnimation.tsx
│ │ ├── index.tsx
│ │ ├── ScaleAnimation.tsx
│ │ └── SlideInAnimation.tsx
│ ├── xmlui-devtools
│ │ ├── .gitignore
│ │ ├── CHANGELOG.md
│ │ ├── demo
│ │ │ └── Main.xmlui
│ │ ├── index.html
│ │ ├── index.ts
│ │ ├── meta
│ │ │ └── componentsMetadata.ts
│ │ ├── package.json
│ │ ├── src
│ │ │ ├── devtools
│ │ │ │ ├── DevTools.tsx
│ │ │ │ ├── DevToolsNative.module.scss
│ │ │ │ ├── DevToolsNative.tsx
│ │ │ │ ├── ModalDialog.module.scss
│ │ │ │ ├── ModalDialog.tsx
│ │ │ │ ├── ModalVisibilityContext.tsx
│ │ │ │ ├── Tooltip.module.scss
│ │ │ │ ├── Tooltip.tsx
│ │ │ │ └── utils.ts
│ │ │ ├── editor
│ │ │ │ └── Editor.tsx
│ │ │ └── index.tsx
│ │ └── vite.config-overrides.ts
│ ├── xmlui-hello-world
│ │ ├── .gitignore
│ │ ├── index.ts
│ │ ├── meta
│ │ │ └── componentsMetadata.ts
│ │ ├── package.json
│ │ └── src
│ │ ├── HelloWorld.module.scss
│ │ ├── HelloWorld.tsx
│ │ ├── HelloWorldNative.tsx
│ │ └── index.tsx
│ ├── xmlui-os-frames
│ │ ├── .gitignore
│ │ ├── demo
│ │ │ └── Main.xmlui
│ │ ├── index.html
│ │ ├── index.ts
│ │ ├── meta
│ │ │ └── componentsMetadata.ts
│ │ ├── package.json
│ │ └── src
│ │ ├── index.tsx
│ │ ├── IPhoneFrame.module.scss
│ │ ├── IPhoneFrame.tsx
│ │ ├── MacOSAppFrame.module.scss
│ │ ├── MacOSAppFrame.tsx
│ │ ├── WindowsAppFrame.module.scss
│ │ └── WindowsAppFrame.tsx
│ ├── xmlui-pdf
│ │ ├── .gitignore
│ │ ├── CHANGELOG.md
│ │ ├── demo
│ │ │ ├── components
│ │ │ │ └── Pdf.xmlui
│ │ │ └── Main.xmlui
│ │ ├── index.html
│ │ ├── index.ts
│ │ ├── meta
│ │ │ └── componentsMetadata.ts
│ │ ├── package.json
│ │ └── src
│ │ ├── index.tsx
│ │ ├── LazyPdfNative.tsx
│ │ ├── Pdf.module.scss
│ │ └── Pdf.tsx
│ ├── xmlui-playground
│ │ ├── .gitignore
│ │ ├── CHANGELOG.md
│ │ ├── demo
│ │ │ └── Main.xmlui
│ │ ├── index.html
│ │ ├── index.ts
│ │ ├── meta
│ │ │ └── componentsMetadata.ts
│ │ ├── package.json
│ │ └── src
│ │ ├── hooks
│ │ │ ├── usePlayground.ts
│ │ │ └── useToast.ts
│ │ ├── index.tsx
│ │ ├── playground
│ │ │ ├── Box.module.scss
│ │ │ ├── Box.tsx
│ │ │ ├── CodeSelector.module.scss
│ │ │ ├── CodeSelector.tsx
│ │ │ ├── ConfirmationDialog.module.scss
│ │ │ ├── ConfirmationDialog.tsx
│ │ │ ├── Editor.tsx
│ │ │ ├── Header.module.scss
│ │ │ ├── Header.tsx
│ │ │ ├── Playground.tsx
│ │ │ ├── PlaygroundContent.module.scss
│ │ │ ├── PlaygroundContent.tsx
│ │ │ ├── PlaygroundNative.module.scss
│ │ │ ├── PlaygroundNative.tsx
│ │ │ ├── Preview.tsx
│ │ │ ├── StandalonePlayground.tsx
│ │ │ ├── StandalonePlaygroundNative.module.scss
│ │ │ ├── StandalonePlaygroundNative.tsx
│ │ │ ├── ThemeSwitcher.module.scss
│ │ │ ├── ThemeSwitcher.tsx
│ │ │ └── utils.ts
│ │ ├── providers
│ │ │ ├── Toast.module.scss
│ │ │ └── ToastProvider.tsx
│ │ ├── state
│ │ │ └── store.ts
│ │ ├── themes
│ │ │ └── theme.ts
│ │ └── utils
│ │ └── helpers.ts
│ ├── xmlui-search
│ │ ├── .gitignore
│ │ ├── CHANGELOG.md
│ │ ├── demo
│ │ │ └── Main.xmlui
│ │ ├── index.html
│ │ ├── index.ts
│ │ ├── meta
│ │ │ └── componentsMetadata.ts
│ │ ├── package.json
│ │ └── src
│ │ ├── index.tsx
│ │ ├── Search.module.scss
│ │ └── Search.tsx
│ ├── xmlui-spreadsheet
│ │ ├── .gitignore
│ │ ├── demo
│ │ │ └── Main.xmlui
│ │ ├── index.html
│ │ ├── index.ts
│ │ ├── meta
│ │ │ └── componentsMetadata.ts
│ │ ├── package.json
│ │ └── src
│ │ ├── index.tsx
│ │ ├── Spreadsheet.tsx
│ │ └── SpreadsheetNative.tsx
│ └── xmlui-website-blocks
│ ├── .gitignore
│ ├── CHANGELOG.md
│ ├── demo
│ │ ├── components
│ │ │ ├── HeroBackgroundBreakoutPage.xmlui
│ │ │ ├── HeroBackgroundsPage.xmlui
│ │ │ ├── HeroContentsPage.xmlui
│ │ │ ├── HeroTextAlignPage.xmlui
│ │ │ ├── HeroTextPage.xmlui
│ │ │ └── HeroTonesPage.xmlui
│ │ ├── Main.xmlui
│ │ └── themes
│ │ └── default.ts
│ ├── index.html
│ ├── index.ts
│ ├── meta
│ │ └── componentsMetadata.ts
│ ├── package.json
│ ├── public
│ │ └── resources
│ │ ├── building.jpg
│ │ └── xmlui-logo.svg
│ └── src
│ ├── Carousel
│ │ ├── Carousel.module.scss
│ │ ├── Carousel.tsx
│ │ ├── CarouselContext.tsx
│ │ └── CarouselNative.tsx
│ ├── FancyButton
│ │ ├── FancyButton.module.scss
│ │ ├── FancyButton.tsx
│ │ └── FancyButton.xmlui
│ ├── Hello
│ │ ├── Hello.tsx
│ │ ├── Hello.xmlui
│ │ └── Hello.xmlui.xs
│ ├── HeroSection
│ │ ├── HeroSection.module.scss
│ │ ├── HeroSection.spec.ts
│ │ ├── HeroSection.tsx
│ │ └── HeroSectionNative.tsx
│ ├── index.tsx
│ ├── ScrollToTop
│ │ ├── ScrollToTop.module.scss
│ │ ├── ScrollToTop.tsx
│ │ └── ScrollToTopNative.tsx
│ └── vite-env.d.ts
├── playwright.config.ts
├── README.md
├── tools
│ ├── codefence
│ │ └── xmlui-code-fence-docs.md
│ ├── create-app
│ │ ├── .gitignore
│ │ ├── CHANGELOG.md
│ │ ├── create-app.ts
│ │ ├── helpers
│ │ │ ├── copy.ts
│ │ │ ├── get-pkg-manager.ts
│ │ │ ├── git.ts
│ │ │ ├── install.ts
│ │ │ ├── is-folder-empty.ts
│ │ │ ├── is-writeable.ts
│ │ │ ├── make-dir.ts
│ │ │ └── validate-pkg.ts
│ │ ├── index.ts
│ │ ├── package.json
│ │ ├── templates
│ │ │ ├── default
│ │ │ │ └── ts
│ │ │ │ ├── gitignore
│ │ │ │ ├── index.html
│ │ │ │ ├── index.ts
│ │ │ │ ├── public
│ │ │ │ │ ├── mockServiceWorker.js
│ │ │ │ │ ├── resources
│ │ │ │ │ │ ├── favicon.ico
│ │ │ │ │ │ └── xmlui-logo.svg
│ │ │ │ │ └── serve.json
│ │ │ │ └── src
│ │ │ │ ├── components
│ │ │ │ │ ├── ApiAware.xmlui
│ │ │ │ │ ├── Home.xmlui
│ │ │ │ │ ├── IncButton.xmlui
│ │ │ │ │ └── PagePanel.xmlui
│ │ │ │ ├── config.ts
│ │ │ │ └── Main.xmlui
│ │ │ ├── index.ts
│ │ │ └── types.ts
│ │ └── tsconfig.json
│ ├── create-xmlui-hello-world
│ │ ├── index.js
│ │ └── package.json
│ └── vscode
│ ├── .gitignore
│ ├── .vscode
│ │ ├── launch.json
│ │ └── tasks.json
│ ├── .vscodeignore
│ ├── build.sh
│ ├── CHANGELOG.md
│ ├── esbuild.js
│ ├── eslint.config.mjs
│ ├── formatter-docs.md
│ ├── generate-test-sample.sh
│ ├── LICENSE.md
│ ├── package-lock.json
│ ├── package.json
│ ├── README.md
│ ├── resources
│ │ ├── xmlui-logo.png
│ │ └── xmlui-markup-syntax-highlighting.png
│ ├── src
│ │ ├── extension.ts
│ │ └── server.ts
│ ├── syntaxes
│ │ └── xmlui.tmLanguage.json
│ ├── test-samples
│ │ └── sample.xmlui
│ ├── tsconfig.json
│ └── tsconfig.tsbuildinfo
├── turbo.json
└── xmlui
├── .gitignore
├── bin
│ ├── bootstrap.cjs
│ ├── bootstrap.js
│ ├── build-lib.ts
│ ├── build.ts
│ ├── index.ts
│ ├── preview.ts
│ ├── start.ts
│ ├── vite-xmlui-plugin.ts
│ └── viteConfig.ts
├── CHANGELOG.md
├── conventions
│ ├── component-qa-checklist.md
│ ├── copilot-conventions.md
│ ├── create-xmlui-components.md
│ ├── mermaid.md
│ ├── testing-conventions.md
│ └── xmlui-in-a-nutshell.md
├── dev-docs
│ ├── accessibility.md
│ ├── build-system.md
│ ├── build-xmlui.md
│ ├── component-behaviors.md
│ ├── component-metadata.md
│ ├── components-with-options.md
│ ├── containers.md
│ ├── data-operations.md
│ ├── glossary.md
│ ├── index.md
│ ├── next
│ │ ├── component-dev-guide.md
│ │ ├── configuration-management-enhancement-summary.md
│ │ ├── documentation-scripts-refactoring-complete-summary.md
│ │ ├── documentation-scripts-refactoring-plan.md
│ │ ├── duplicate-pattern-extraction-summary.md
│ │ ├── error-handling-standardization-summary.md
│ │ ├── generating-component-reference.md
│ │ ├── index.md
│ │ ├── logging-consistency-implementation-summary.md
│ │ ├── project-build.md
│ │ ├── project-structure.md
│ │ ├── theme-context.md
│ │ ├── tiptap-design-considerations.md
│ │ ├── working-with-code.md
│ │ ├── xmlui-runtime-architecture
│ │ └── xmlui-wcag-accessibility-report.md
│ ├── react-fundamentals.md
│ ├── release-method.md
│ ├── standalone-app.md
│ ├── theme-variables-refactoring.md
│ ├── ud-components.md
│ └── xmlui-repo.md
├── package.json
├── scripts
│ ├── coverage-only.js
│ ├── e2e-test-summary.js
│ ├── extract-component-metadata.js
│ ├── generate-docs
│ │ ├── build-downloads-map.mjs
│ │ ├── build-pages-map.mjs
│ │ ├── components-config.json
│ │ ├── configuration-management.mjs
│ │ ├── constants.mjs
│ │ ├── create-theme-files.mjs
│ │ ├── DocsGenerator.mjs
│ │ ├── error-handling.mjs
│ │ ├── extensions-config.json
│ │ ├── folders.mjs
│ │ ├── generate-summary-files.mjs
│ │ ├── get-docs.mjs
│ │ ├── input-handler.mjs
│ │ ├── logger.mjs
│ │ ├── logging-standards.mjs
│ │ ├── MetadataProcessor.mjs
│ │ ├── pattern-utilities.mjs
│ │ └── utils.mjs
│ ├── generate-metadata-markdown.js
│ ├── get-langserver-metadata.js
│ ├── inline-links.mjs
│ └── README-e2e-summary.md
├── src
│ ├── abstractions
│ │ ├── _conventions.md
│ │ ├── ActionDefs.ts
│ │ ├── AppContextDefs.ts
│ │ ├── ComponentDefs.ts
│ │ ├── ContainerDefs.ts
│ │ ├── ExtensionDefs.ts
│ │ ├── FunctionDefs.ts
│ │ ├── RendererDefs.ts
│ │ ├── scripting
│ │ │ ├── BlockScope.ts
│ │ │ ├── Compilation.ts
│ │ │ ├── LogicalThread.ts
│ │ │ ├── LoopScope.ts
│ │ │ ├── modules.ts
│ │ │ ├── ScriptParserError.ts
│ │ │ ├── Token.ts
│ │ │ ├── TryScope.ts
│ │ │ └── TryScopeExp.ts
│ │ └── ThemingDefs.ts
│ ├── components
│ │ ├── _conventions.md
│ │ ├── abstractions.ts
│ │ ├── Accordion
│ │ │ ├── Accordion.md
│ │ │ ├── Accordion.module.scss
│ │ │ ├── Accordion.spec.ts
│ │ │ ├── Accordion.tsx
│ │ │ ├── AccordionContext.tsx
│ │ │ ├── AccordionItem.tsx
│ │ │ ├── AccordionItemNative.tsx
│ │ │ └── AccordionNative.tsx
│ │ ├── Animation
│ │ │ └── AnimationNative.tsx
│ │ ├── APICall
│ │ │ ├── APICall.md
│ │ │ ├── APICall.spec.ts
│ │ │ ├── APICall.tsx
│ │ │ └── APICallNative.tsx
│ │ ├── App
│ │ │ ├── App.md
│ │ │ ├── App.module.scss
│ │ │ ├── App.spec.ts
│ │ │ ├── App.tsx
│ │ │ ├── AppLayoutContext.ts
│ │ │ ├── AppNative.tsx
│ │ │ ├── AppStateContext.ts
│ │ │ ├── doc-resources
│ │ │ │ ├── condensed-sticky.xmlui
│ │ │ │ ├── condensed.xmlui
│ │ │ │ ├── horizontal-sticky.xmlui
│ │ │ │ ├── horizontal.xmlui
│ │ │ │ ├── vertical-full-header.xmlui
│ │ │ │ ├── vertical-sticky.xmlui
│ │ │ │ └── vertical.xmlui
│ │ │ ├── IndexerContext.ts
│ │ │ ├── LinkInfoContext.ts
│ │ │ ├── SearchContext.tsx
│ │ │ ├── Sheet.module.scss
│ │ │ └── Sheet.tsx
│ │ ├── AppHeader
│ │ │ ├── AppHeader.md
│ │ │ ├── AppHeader.module.scss
│ │ │ ├── AppHeader.spec.ts
│ │ │ ├── AppHeader.tsx
│ │ │ └── AppHeaderNative.tsx
│ │ ├── AppState
│ │ │ ├── AppState.md
│ │ │ ├── AppState.spec.ts
│ │ │ ├── AppState.tsx
│ │ │ └── AppStateNative.tsx
│ │ ├── AutoComplete
│ │ │ ├── AutoComplete.md
│ │ │ ├── AutoComplete.module.scss
│ │ │ ├── AutoComplete.spec.ts
│ │ │ ├── AutoComplete.tsx
│ │ │ ├── AutoCompleteContext.tsx
│ │ │ └── AutoCompleteNative.tsx
│ │ ├── Avatar
│ │ │ ├── Avatar.md
│ │ │ ├── Avatar.module.scss
│ │ │ ├── Avatar.spec.ts
│ │ │ ├── Avatar.tsx
│ │ │ └── AvatarNative.tsx
│ │ ├── Backdrop
│ │ │ ├── Backdrop.md
│ │ │ ├── Backdrop.module.scss
│ │ │ ├── Backdrop.spec.ts
│ │ │ ├── Backdrop.tsx
│ │ │ └── BackdropNative.tsx
│ │ ├── Badge
│ │ │ ├── Badge.md
│ │ │ ├── Badge.module.scss
│ │ │ ├── Badge.spec.ts
│ │ │ ├── Badge.tsx
│ │ │ └── BadgeNative.tsx
│ │ ├── Bookmark
│ │ │ ├── Bookmark.md
│ │ │ ├── Bookmark.module.scss
│ │ │ ├── Bookmark.spec.ts
│ │ │ ├── Bookmark.tsx
│ │ │ └── BookmarkNative.tsx
│ │ ├── Breakout
│ │ │ ├── Breakout.module.scss
│ │ │ ├── Breakout.spec.ts
│ │ │ ├── Breakout.tsx
│ │ │ └── BreakoutNative.tsx
│ │ ├── Button
│ │ │ ├── Button-style.spec.ts
│ │ │ ├── Button.md
│ │ │ ├── Button.module.scss
│ │ │ ├── Button.spec.ts
│ │ │ ├── Button.tsx
│ │ │ └── ButtonNative.tsx
│ │ ├── Card
│ │ │ ├── Card.md
│ │ │ ├── Card.module.scss
│ │ │ ├── Card.spec.ts
│ │ │ ├── Card.tsx
│ │ │ └── CardNative.tsx
│ │ ├── Carousel
│ │ │ ├── Carousel.md
│ │ │ ├── Carousel.module.scss
│ │ │ ├── Carousel.spec.ts
│ │ │ ├── Carousel.tsx
│ │ │ ├── CarouselContext.tsx
│ │ │ ├── CarouselItem.tsx
│ │ │ ├── CarouselItemNative.tsx
│ │ │ └── CarouselNative.tsx
│ │ ├── ChangeListener
│ │ │ ├── ChangeListener.md
│ │ │ ├── ChangeListener.spec.ts
│ │ │ ├── ChangeListener.tsx
│ │ │ └── ChangeListenerNative.tsx
│ │ ├── chart-color-schemes.ts
│ │ ├── Charts
│ │ │ ├── AreaChart
│ │ │ │ ├── AreaChart.md
│ │ │ │ ├── AreaChart.spec.ts
│ │ │ │ ├── AreaChart.tsx
│ │ │ │ └── AreaChartNative.tsx
│ │ │ ├── BarChart
│ │ │ │ ├── BarChart.md
│ │ │ │ ├── BarChart.module.scss
│ │ │ │ ├── BarChart.spec.ts
│ │ │ │ ├── BarChart.tsx
│ │ │ │ └── BarChartNative.tsx
│ │ │ ├── DonutChart
│ │ │ │ ├── DonutChart.spec.ts
│ │ │ │ └── DonutChart.tsx
│ │ │ ├── LabelList
│ │ │ │ ├── LabelList.module.scss
│ │ │ │ ├── LabelList.spec.ts
│ │ │ │ ├── LabelList.tsx
│ │ │ │ └── LabelListNative.tsx
│ │ │ ├── Legend
│ │ │ │ ├── Legend.spec.ts
│ │ │ │ ├── Legend.tsx
│ │ │ │ └── LegendNative.tsx
│ │ │ ├── LineChart
│ │ │ │ ├── LineChart.md
│ │ │ │ ├── LineChart.module.scss
│ │ │ │ ├── LineChart.spec.ts
│ │ │ │ ├── LineChart.tsx
│ │ │ │ └── LineChartNative.tsx
│ │ │ ├── PieChart
│ │ │ │ ├── PieChart.md
│ │ │ │ ├── PieChart.spec.ts
│ │ │ │ ├── PieChart.tsx
│ │ │ │ ├── PieChartNative.module.scss
│ │ │ │ └── PieChartNative.tsx
│ │ │ ├── RadarChart
│ │ │ │ ├── RadarChart.md
│ │ │ │ ├── RadarChart.spec.ts
│ │ │ │ ├── RadarChart.tsx
│ │ │ │ └── RadarChartNative.tsx
│ │ │ ├── Tooltip
│ │ │ │ ├── TooltipContent.module.scss
│ │ │ │ ├── TooltipContent.spec.ts
│ │ │ │ └── TooltipContent.tsx
│ │ │ └── utils
│ │ │ ├── abstractions.ts
│ │ │ └── ChartProvider.tsx
│ │ ├── Checkbox
│ │ │ ├── Checkbox.md
│ │ │ ├── Checkbox.spec.ts
│ │ │ └── Checkbox.tsx
│ │ ├── CodeBlock
│ │ │ ├── CodeBlock.module.scss
│ │ │ ├── CodeBlock.spec.ts
│ │ │ ├── CodeBlock.tsx
│ │ │ ├── CodeBlockNative.tsx
│ │ │ └── highlight-code.ts
│ │ ├── collectedComponentMetadata.ts
│ │ ├── ColorPicker
│ │ │ ├── ColorPicker.md
│ │ │ ├── ColorPicker.module.scss
│ │ │ ├── ColorPicker.spec.ts
│ │ │ ├── ColorPicker.tsx
│ │ │ └── ColorPickerNative.tsx
│ │ ├── Column
│ │ │ ├── Column.md
│ │ │ ├── Column.tsx
│ │ │ ├── ColumnNative.tsx
│ │ │ ├── doc-resources
│ │ │ │ └── list-component-data.js
│ │ │ └── TableContext.tsx
│ │ ├── component-utils.ts
│ │ ├── ComponentProvider.tsx
│ │ ├── ComponentRegistryContext.tsx
│ │ ├── container-helpers.tsx
│ │ ├── ContentSeparator
│ │ │ ├── ContentSeparator.md
│ │ │ ├── ContentSeparator.module.scss
│ │ │ ├── ContentSeparator.spec.ts
│ │ │ ├── ContentSeparator.tsx
│ │ │ ├── ContentSeparatorNative.tsx
│ │ │ └── test-padding.xmlui
│ │ ├── DataSource
│ │ │ ├── DataSource.md
│ │ │ └── DataSource.tsx
│ │ ├── DateInput
│ │ │ ├── DateInput.md
│ │ │ ├── DateInput.module.scss
│ │ │ ├── DateInput.spec.ts
│ │ │ ├── DateInput.tsx
│ │ │ └── DateInputNative.tsx
│ │ ├── DatePicker
│ │ │ ├── DatePicker.md
│ │ │ ├── DatePicker.module.scss
│ │ │ ├── DatePicker.spec.ts
│ │ │ ├── DatePicker.tsx
│ │ │ └── DatePickerNative.tsx
│ │ ├── DropdownMenu
│ │ │ ├── DropdownMenu.md
│ │ │ ├── DropdownMenu.module.scss
│ │ │ ├── DropdownMenu.spec.ts
│ │ │ ├── DropdownMenu.tsx
│ │ │ ├── DropdownMenuNative.tsx
│ │ │ ├── MenuItem.md
│ │ │ └── SubMenuItem.md
│ │ ├── EmojiSelector
│ │ │ ├── EmojiSelector.md
│ │ │ ├── EmojiSelector.spec.ts
│ │ │ ├── EmojiSelector.tsx
│ │ │ └── EmojiSelectorNative.tsx
│ │ ├── ExpandableItem
│ │ │ ├── ExpandableItem.module.scss
│ │ │ ├── ExpandableItem.spec.ts
│ │ │ ├── ExpandableItem.tsx
│ │ │ └── ExpandableItemNative.tsx
│ │ ├── FileInput
│ │ │ ├── FileInput.md
│ │ │ ├── FileInput.module.scss
│ │ │ ├── FileInput.spec.ts
│ │ │ ├── FileInput.tsx
│ │ │ └── FileInputNative.tsx
│ │ ├── FileUploadDropZone
│ │ │ ├── FileUploadDropZone.md
│ │ │ ├── FileUploadDropZone.module.scss
│ │ │ ├── FileUploadDropZone.spec.ts
│ │ │ ├── FileUploadDropZone.tsx
│ │ │ └── FileUploadDropZoneNative.tsx
│ │ ├── FlowLayout
│ │ │ ├── FlowLayout.md
│ │ │ ├── FlowLayout.module.scss
│ │ │ ├── FlowLayout.spec.ts
│ │ │ ├── FlowLayout.spec.ts-snapshots
│ │ │ │ └── Edge-cases-boxShadow-is-not-clipped-1-non-smoke-darwin.png
│ │ │ ├── FlowLayout.tsx
│ │ │ └── FlowLayoutNative.tsx
│ │ ├── Footer
│ │ │ ├── Footer.md
│ │ │ ├── Footer.module.scss
│ │ │ ├── Footer.spec.ts
│ │ │ ├── Footer.tsx
│ │ │ └── FooterNative.tsx
│ │ ├── Form
│ │ │ ├── Form.md
│ │ │ ├── Form.module.scss
│ │ │ ├── Form.spec.ts
│ │ │ ├── Form.tsx
│ │ │ ├── formActions.ts
│ │ │ ├── FormContext.ts
│ │ │ └── FormNative.tsx
│ │ ├── FormItem
│ │ │ ├── FormItem.md
│ │ │ ├── FormItem.module.scss
│ │ │ ├── FormItem.spec.ts
│ │ │ ├── FormItem.tsx
│ │ │ ├── FormItemNative.tsx
│ │ │ ├── HelperText.module.scss
│ │ │ ├── HelperText.tsx
│ │ │ ├── ItemWithLabel.tsx
│ │ │ └── Validations.ts
│ │ ├── FormSection
│ │ │ ├── FormSection.md
│ │ │ ├── FormSection.ts
│ │ │ └── FormSection.xmlui
│ │ ├── Fragment
│ │ │ ├── Fragment.spec.ts
│ │ │ └── Fragment.tsx
│ │ ├── Heading
│ │ │ ├── abstractions.ts
│ │ │ ├── H1.md
│ │ │ ├── H1.spec.ts
│ │ │ ├── H2.md
│ │ │ ├── H2.spec.ts
│ │ │ ├── H3.md
│ │ │ ├── H3.spec.ts
│ │ │ ├── H4.md
│ │ │ ├── H4.spec.ts
│ │ │ ├── H5.md
│ │ │ ├── H5.spec.ts
│ │ │ ├── H6.md
│ │ │ ├── H6.spec.ts
│ │ │ ├── Heading.md
│ │ │ ├── Heading.module.scss
│ │ │ ├── Heading.spec.ts
│ │ │ ├── Heading.tsx
│ │ │ └── HeadingNative.tsx
│ │ ├── HoverCard
│ │ │ ├── HoverCard.tsx
│ │ │ └── HovercardNative.tsx
│ │ ├── HtmlTags
│ │ │ ├── HtmlTags.module.scss
│ │ │ ├── HtmlTags.spec.ts
│ │ │ └── HtmlTags.tsx
│ │ ├── Icon
│ │ │ ├── AdmonitionDanger.tsx
│ │ │ ├── AdmonitionInfo.tsx
│ │ │ ├── AdmonitionNote.tsx
│ │ │ ├── AdmonitionTip.tsx
│ │ │ ├── AdmonitionWarning.tsx
│ │ │ ├── ApiIcon.tsx
│ │ │ ├── ArrowDropDown.module.scss
│ │ │ ├── ArrowDropDown.tsx
│ │ │ ├── ArrowDropUp.module.scss
│ │ │ ├── ArrowDropUp.tsx
│ │ │ ├── ArrowLeft.module.scss
│ │ │ ├── ArrowLeft.tsx
│ │ │ ├── ArrowRight.module.scss
│ │ │ ├── ArrowRight.tsx
│ │ │ ├── Attach.tsx
│ │ │ ├── Binding.module.scss
│ │ │ ├── Binding.tsx
│ │ │ ├── BoardIcon.tsx
│ │ │ ├── BoxIcon.tsx
│ │ │ ├── CheckIcon.tsx
│ │ │ ├── ChevronDownIcon.tsx
│ │ │ ├── ChevronLeft.tsx
│ │ │ ├── ChevronRight.tsx
│ │ │ ├── ChevronUpIcon.tsx
│ │ │ ├── CodeFileIcon.tsx
│ │ │ ├── CodeSandbox.tsx
│ │ │ ├── CompactListIcon.tsx
│ │ │ ├── ContentCopyIcon.tsx
│ │ │ ├── DarkToLightIcon.tsx
│ │ │ ├── DatabaseIcon.module.scss
│ │ │ ├── DatabaseIcon.tsx
│ │ │ ├── DocFileIcon.tsx
│ │ │ ├── DocIcon.tsx
│ │ │ ├── DotMenuHorizontalIcon.tsx
│ │ │ ├── DotMenuIcon.tsx
│ │ │ ├── EmailIcon.tsx
│ │ │ ├── EmptyFolderIcon.tsx
│ │ │ ├── ErrorIcon.tsx
│ │ │ ├── ExpressionIcon.tsx
│ │ │ ├── FillPlusCricleIcon.tsx
│ │ │ ├── FilterIcon.tsx
│ │ │ ├── FolderIcon.tsx
│ │ │ ├── GlobeIcon.tsx
│ │ │ ├── HomeIcon.tsx
│ │ │ ├── HyperLinkIcon.tsx
│ │ │ ├── Icon.md
│ │ │ ├── Icon.module.scss
│ │ │ ├── Icon.spec.ts
│ │ │ ├── Icon.tsx
│ │ │ ├── IconNative.tsx
│ │ │ ├── ImageFileIcon.tsx
│ │ │ ├── Inspect.tsx
│ │ │ ├── LightToDark.tsx
│ │ │ ├── LinkIcon.tsx
│ │ │ ├── ListIcon.tsx
│ │ │ ├── LooseListIcon.tsx
│ │ │ ├── MoonIcon.tsx
│ │ │ ├── MoreOptionsIcon.tsx
│ │ │ ├── NoSortIcon.tsx
│ │ │ ├── PDFIcon.tsx
│ │ │ ├── PenIcon.tsx
│ │ │ ├── PhoneIcon.tsx
│ │ │ ├── PhotoIcon.tsx
│ │ │ ├── PlusIcon.tsx
│ │ │ ├── SearchIcon.tsx
│ │ │ ├── ShareIcon.tsx
│ │ │ ├── SortAscendingIcon.tsx
│ │ │ ├── SortDescendingIcon.tsx
│ │ │ ├── StarsIcon.tsx
│ │ │ ├── SunIcon.tsx
│ │ │ ├── svg
│ │ │ │ ├── admonition_danger.svg
│ │ │ │ ├── admonition_info.svg
│ │ │ │ ├── admonition_note.svg
│ │ │ │ ├── admonition_tip.svg
│ │ │ │ ├── admonition_warning.svg
│ │ │ │ ├── api.svg
│ │ │ │ ├── arrow-dropdown.svg
│ │ │ │ ├── arrow-left.svg
│ │ │ │ ├── arrow-right.svg
│ │ │ │ ├── arrow-up.svg
│ │ │ │ ├── attach.svg
│ │ │ │ ├── binding.svg
│ │ │ │ ├── box.svg
│ │ │ │ ├── bulb.svg
│ │ │ │ ├── code-file.svg
│ │ │ │ ├── code-sandbox.svg
│ │ │ │ ├── dark_to_light.svg
│ │ │ │ ├── database.svg
│ │ │ │ ├── doc.svg
│ │ │ │ ├── empty-folder.svg
│ │ │ │ ├── expression.svg
│ │ │ │ ├── eye-closed.svg
│ │ │ │ ├── eye-dark.svg
│ │ │ │ ├── eye.svg
│ │ │ │ ├── file-text.svg
│ │ │ │ ├── filter.svg
│ │ │ │ ├── folder.svg
│ │ │ │ ├── img.svg
│ │ │ │ ├── inspect.svg
│ │ │ │ ├── light_to_dark.svg
│ │ │ │ ├── moon.svg
│ │ │ │ ├── pdf.svg
│ │ │ │ ├── photo.svg
│ │ │ │ ├── share.svg
│ │ │ │ ├── stars.svg
│ │ │ │ ├── sun.svg
│ │ │ │ ├── trending-down.svg
│ │ │ │ ├── trending-level.svg
│ │ │ │ ├── trending-up.svg
│ │ │ │ ├── txt.svg
│ │ │ │ ├── unknown-file.svg
│ │ │ │ ├── unlink.svg
│ │ │ │ └── xls.svg
│ │ │ ├── TableDeleteColumnIcon.tsx
│ │ │ ├── TableDeleteRowIcon.tsx
│ │ │ ├── TableInsertColumnIcon.tsx
│ │ │ ├── TableInsertRowIcon.tsx
│ │ │ ├── TrashIcon.tsx
│ │ │ ├── TrendingDownIcon.tsx
│ │ │ ├── TrendingLevelIcon.tsx
│ │ │ ├── TrendingUpIcon.tsx
│ │ │ ├── TxtIcon.tsx
│ │ │ ├── UnknownFileIcon.tsx
│ │ │ ├── UnlinkIcon.tsx
│ │ │ ├── UserIcon.tsx
│ │ │ ├── WarningIcon.tsx
│ │ │ └── XlsIcon.tsx
│ │ ├── IconProvider.tsx
│ │ ├── IconRegistryContext.tsx
│ │ ├── IFrame
│ │ │ ├── IFrame.md
│ │ │ ├── IFrame.module.scss
│ │ │ ├── IFrame.spec.ts
│ │ │ ├── IFrame.tsx
│ │ │ └── IFrameNative.tsx
│ │ ├── Image
│ │ │ ├── Image.md
│ │ │ ├── Image.module.scss
│ │ │ ├── Image.spec.ts
│ │ │ ├── Image.tsx
│ │ │ └── ImageNative.tsx
│ │ ├── Input
│ │ │ ├── index.ts
│ │ │ ├── InputAdornment.module.scss
│ │ │ ├── InputAdornment.tsx
│ │ │ ├── InputDivider.module.scss
│ │ │ ├── InputDivider.tsx
│ │ │ ├── InputLabel.module.scss
│ │ │ ├── InputLabel.tsx
│ │ │ ├── PartialInput.module.scss
│ │ │ └── PartialInput.tsx
│ │ ├── InspectButton
│ │ │ ├── InspectButton.module.scss
│ │ │ └── InspectButton.tsx
│ │ ├── Items
│ │ │ ├── Items.md
│ │ │ ├── Items.spec.ts
│ │ │ ├── Items.tsx
│ │ │ └── ItemsNative.tsx
│ │ ├── Link
│ │ │ ├── Link.md
│ │ │ ├── Link.module.scss
│ │ │ ├── Link.spec.ts
│ │ │ ├── Link.tsx
│ │ │ └── LinkNative.tsx
│ │ ├── List
│ │ │ ├── doc-resources
│ │ │ │ └── list-component-data.js
│ │ │ ├── List.md
│ │ │ ├── List.module.scss
│ │ │ ├── List.spec.ts
│ │ │ ├── List.tsx
│ │ │ └── ListNative.tsx
│ │ ├── Logo
│ │ │ ├── doc-resources
│ │ │ │ └── xmlui-logo.svg
│ │ │ ├── Logo.md
│ │ │ ├── Logo.tsx
│ │ │ └── LogoNative.tsx
│ │ ├── Markdown
│ │ │ ├── CodeText.module.scss
│ │ │ ├── CodeText.tsx
│ │ │ ├── Markdown.md
│ │ │ ├── Markdown.module.scss
│ │ │ ├── Markdown.spec.ts
│ │ │ ├── Markdown.tsx
│ │ │ ├── MarkdownNative.tsx
│ │ │ ├── parse-binding-expr.ts
│ │ │ └── utils.ts
│ │ ├── metadata-helpers.ts
│ │ ├── ModalDialog
│ │ │ ├── ConfirmationModalContextProvider.tsx
│ │ │ ├── Dialog.module.scss
│ │ │ ├── Dialog.tsx
│ │ │ ├── ModalDialog.md
│ │ │ ├── ModalDialog.module.scss
│ │ │ ├── ModalDialog.spec.ts
│ │ │ ├── ModalDialog.tsx
│ │ │ ├── ModalDialogNative.tsx
│ │ │ └── ModalVisibilityContext.tsx
│ │ ├── NavGroup
│ │ │ ├── NavGroup.md
│ │ │ ├── NavGroup.module.scss
│ │ │ ├── NavGroup.spec.ts
│ │ │ ├── NavGroup.tsx
│ │ │ ├── NavGroupContext.ts
│ │ │ └── NavGroupNative.tsx
│ │ ├── NavLink
│ │ │ ├── NavLink.md
│ │ │ ├── NavLink.module.scss
│ │ │ ├── NavLink.spec.ts
│ │ │ ├── NavLink.tsx
│ │ │ └── NavLinkNative.tsx
│ │ ├── NavPanel
│ │ │ ├── NavPanel.md
│ │ │ ├── NavPanel.module.scss
│ │ │ ├── NavPanel.spec.ts
│ │ │ ├── NavPanel.tsx
│ │ │ └── NavPanelNative.tsx
│ │ ├── NestedApp
│ │ │ ├── AppWithCodeView.module.scss
│ │ │ ├── AppWithCodeView.tsx
│ │ │ ├── AppWithCodeViewNative.tsx
│ │ │ ├── defaultProps.tsx
│ │ │ ├── logo.svg
│ │ │ ├── NestedApp.module.scss
│ │ │ ├── NestedApp.tsx
│ │ │ ├── NestedAppNative.tsx
│ │ │ ├── Tooltip.module.scss
│ │ │ ├── Tooltip.tsx
│ │ │ └── utils.ts
│ │ ├── NoResult
│ │ │ ├── NoResult.md
│ │ │ ├── NoResult.module.scss
│ │ │ ├── NoResult.spec.ts
│ │ │ ├── NoResult.tsx
│ │ │ └── NoResultNative.tsx
│ │ ├── NumberBox
│ │ │ ├── numberbox-abstractions.ts
│ │ │ ├── NumberBox.md
│ │ │ ├── NumberBox.module.scss
│ │ │ ├── NumberBox.spec.ts
│ │ │ ├── NumberBox.tsx
│ │ │ └── NumberBoxNative.tsx
│ │ ├── Option
│ │ │ ├── Option.md
│ │ │ ├── Option.spec.ts
│ │ │ ├── Option.tsx
│ │ │ ├── OptionNative.tsx
│ │ │ └── OptionTypeProvider.tsx
│ │ ├── PageMetaTitle
│ │ │ ├── PageMetaTilteNative.tsx
│ │ │ ├── PageMetaTitle.md
│ │ │ ├── PageMetaTitle.spec.ts
│ │ │ └── PageMetaTitle.tsx
│ │ ├── Pages
│ │ │ ├── Page.md
│ │ │ ├── Pages.md
│ │ │ ├── Pages.module.scss
│ │ │ ├── Pages.tsx
│ │ │ └── PagesNative.tsx
│ │ ├── Pagination
│ │ │ ├── Pagination.md
│ │ │ ├── Pagination.module.scss
│ │ │ ├── Pagination.spec.ts
│ │ │ ├── Pagination.tsx
│ │ │ └── PaginationNative.tsx
│ │ ├── PositionedContainer
│ │ │ ├── PositionedContainer.module.scss
│ │ │ ├── PositionedContainer.tsx
│ │ │ └── PositionedContainerNative.tsx
│ │ ├── ProfileMenu
│ │ │ ├── ProfileMenu.module.scss
│ │ │ └── ProfileMenu.tsx
│ │ ├── ProgressBar
│ │ │ ├── ProgressBar.md
│ │ │ ├── ProgressBar.module.scss
│ │ │ ├── ProgressBar.spec.ts
│ │ │ ├── ProgressBar.tsx
│ │ │ └── ProgressBarNative.tsx
│ │ ├── Queue
│ │ │ ├── Queue.md
│ │ │ ├── Queue.spec.ts
│ │ │ ├── Queue.tsx
│ │ │ ├── queueActions.ts
│ │ │ └── QueueNative.tsx
│ │ ├── RadioGroup
│ │ │ ├── RadioGroup.md
│ │ │ ├── RadioGroup.module.scss
│ │ │ ├── RadioGroup.spec.ts
│ │ │ ├── RadioGroup.tsx
│ │ │ ├── RadioGroupNative.tsx
│ │ │ ├── RadioItem.tsx
│ │ │ └── RadioItemNative.tsx
│ │ ├── RealTimeAdapter
│ │ │ ├── RealTimeAdapter.tsx
│ │ │ └── RealTimeAdapterNative.tsx
│ │ ├── Redirect
│ │ │ ├── Redirect.md
│ │ │ ├── Redirect.spec.ts
│ │ │ └── Redirect.tsx
│ │ ├── ResponsiveBar
│ │ │ ├── README.md
│ │ │ ├── ResponsiveBar.md
│ │ │ ├── ResponsiveBar.module.scss
│ │ │ ├── ResponsiveBar.spec.ts
│ │ │ ├── ResponsiveBar.tsx
│ │ │ └── ResponsiveBarNative.tsx
│ │ ├── Select
│ │ │ ├── HiddenOption.tsx
│ │ │ ├── OptionContext.ts
│ │ │ ├── Select.md
│ │ │ ├── Select.module.scss
│ │ │ ├── Select.spec.ts
│ │ │ ├── Select.tsx
│ │ │ ├── SelectContext.tsx
│ │ │ └── SelectNative.tsx
│ │ ├── SelectionStore
│ │ │ ├── SelectionStore.md
│ │ │ ├── SelectionStore.tsx
│ │ │ └── SelectionStoreNative.tsx
│ │ ├── Slider
│ │ │ ├── Slider.md
│ │ │ ├── Slider.module.scss
│ │ │ ├── Slider.spec.ts
│ │ │ ├── Slider.tsx
│ │ │ └── SliderNative.tsx
│ │ ├── Slot
│ │ │ ├── Slot.md
│ │ │ ├── Slot.spec.ts
│ │ │ └── Slot.ts
│ │ ├── SlotItem.tsx
│ │ ├── SpaceFiller
│ │ │ ├── SpaceFiller.md
│ │ │ ├── SpaceFiller.module.scss
│ │ │ ├── SpaceFiller.spec.ts
│ │ │ ├── SpaceFiller.tsx
│ │ │ └── SpaceFillerNative.tsx
│ │ ├── Spinner
│ │ │ ├── Spinner.md
│ │ │ ├── Spinner.module.scss
│ │ │ ├── Spinner.spec.ts
│ │ │ ├── Spinner.tsx
│ │ │ └── SpinnerNative.tsx
│ │ ├── Splitter
│ │ │ ├── HSplitter.md
│ │ │ ├── HSplitter.spec.ts
│ │ │ ├── Splitter.md
│ │ │ ├── Splitter.module.scss
│ │ │ ├── Splitter.spec.ts
│ │ │ ├── Splitter.tsx
│ │ │ ├── SplitterNative.tsx
│ │ │ ├── utils.ts
│ │ │ ├── VSplitter.md
│ │ │ └── VSplitter.spec.ts
│ │ ├── Stack
│ │ │ ├── CHStack.md
│ │ │ ├── CHStack.spec.ts
│ │ │ ├── CVStack.md
│ │ │ ├── CVStack.spec.ts
│ │ │ ├── HStack.md
│ │ │ ├── HStack.spec.ts
│ │ │ ├── Stack.md
│ │ │ ├── Stack.module.scss
│ │ │ ├── Stack.spec.ts
│ │ │ ├── Stack.tsx
│ │ │ ├── StackNative.tsx
│ │ │ ├── VStack.md
│ │ │ └── VStack.spec.ts
│ │ ├── StickyBox
│ │ │ ├── StickyBox.md
│ │ │ ├── StickyBox.module.scss
│ │ │ ├── StickyBox.tsx
│ │ │ └── StickyBoxNative.tsx
│ │ ├── Switch
│ │ │ ├── Switch.md
│ │ │ ├── Switch.spec.ts
│ │ │ └── Switch.tsx
│ │ ├── Table
│ │ │ ├── doc-resources
│ │ │ │ └── list-component-data.js
│ │ │ ├── react-table-config.d.ts
│ │ │ ├── Table.md
│ │ │ ├── Table.module.scss
│ │ │ ├── Table.spec.ts
│ │ │ ├── Table.tsx
│ │ │ ├── TableNative.tsx
│ │ │ └── useRowSelection.tsx
│ │ ├── TableOfContents
│ │ │ ├── TableOfContents.module.scss
│ │ │ ├── TableOfContents.spec.ts
│ │ │ ├── TableOfContents.tsx
│ │ │ └── TableOfContentsNative.tsx
│ │ ├── Tabs
│ │ │ ├── TabContext.tsx
│ │ │ ├── TabItem.md
│ │ │ ├── TabItem.tsx
│ │ │ ├── TabItemNative.tsx
│ │ │ ├── Tabs.md
│ │ │ ├── Tabs.module.scss
│ │ │ ├── Tabs.spec.ts
│ │ │ ├── Tabs.tsx
│ │ │ └── TabsNative.tsx
│ │ ├── Text
│ │ │ ├── Text.md
│ │ │ ├── Text.module.scss
│ │ │ ├── Text.spec.ts
│ │ │ ├── Text.tsx
│ │ │ └── TextNative.tsx
│ │ ├── TextArea
│ │ │ ├── TextArea.md
│ │ │ ├── TextArea.module.scss
│ │ │ ├── TextArea.spec.ts
│ │ │ ├── TextArea.tsx
│ │ │ ├── TextAreaNative.tsx
│ │ │ ├── TextAreaResizable.tsx
│ │ │ └── useComposedRef.ts
│ │ ├── TextBox
│ │ │ ├── TextBox.md
│ │ │ ├── TextBox.module.scss
│ │ │ ├── TextBox.spec.ts
│ │ │ ├── TextBox.tsx
│ │ │ └── TextBoxNative.tsx
│ │ ├── Theme
│ │ │ ├── NotificationToast.tsx
│ │ │ ├── Theme.md
│ │ │ ├── Theme.module.scss
│ │ │ ├── Theme.spec.ts
│ │ │ ├── Theme.tsx
│ │ │ └── ThemeNative.tsx
│ │ ├── TimeInput
│ │ │ ├── TimeInput.md
│ │ │ ├── TimeInput.module.scss
│ │ │ ├── TimeInput.spec.ts
│ │ │ ├── TimeInput.tsx
│ │ │ ├── TimeInputNative.tsx
│ │ │ └── utils.ts
│ │ ├── Timer
│ │ │ ├── Timer.md
│ │ │ ├── Timer.spec.ts
│ │ │ ├── Timer.tsx
│ │ │ └── TimerNative.tsx
│ │ ├── Toggle
│ │ │ ├── Toggle.module.scss
│ │ │ └── Toggle.tsx
│ │ ├── ToneChangerButton
│ │ │ ├── ToneChangerButton.md
│ │ │ ├── ToneChangerButton.spec.ts
│ │ │ └── ToneChangerButton.tsx
│ │ ├── ToneSwitch
│ │ │ ├── ToneSwitch.md
│ │ │ ├── ToneSwitch.module.scss
│ │ │ ├── ToneSwitch.spec.ts
│ │ │ ├── ToneSwitch.tsx
│ │ │ └── ToneSwitchNative.tsx
│ │ ├── Tooltip
│ │ │ ├── Tooltip.md
│ │ │ ├── Tooltip.module.scss
│ │ │ ├── Tooltip.spec.ts
│ │ │ ├── Tooltip.tsx
│ │ │ └── TooltipNative.tsx
│ │ ├── Tree
│ │ │ ├── testData.ts
│ │ │ ├── Tree-dynamic.spec.ts
│ │ │ ├── Tree-icons.spec.ts
│ │ │ ├── Tree.md
│ │ │ ├── Tree.spec.ts
│ │ │ ├── TreeComponent.module.scss
│ │ │ ├── TreeComponent.tsx
│ │ │ └── TreeNative.tsx
│ │ ├── TreeDisplay
│ │ │ ├── TreeDisplay.md
│ │ │ ├── TreeDisplay.module.scss
│ │ │ ├── TreeDisplay.tsx
│ │ │ └── TreeDisplayNative.tsx
│ │ ├── ValidationSummary
│ │ │ ├── ValidationSummary.module.scss
│ │ │ └── ValidationSummary.tsx
│ │ └── VisuallyHidden.tsx
│ ├── components-core
│ │ ├── abstractions
│ │ │ ├── ComponentRenderer.ts
│ │ │ ├── LoaderRenderer.ts
│ │ │ ├── standalone.ts
│ │ │ └── treeAbstractions.ts
│ │ ├── action
│ │ │ ├── actions.ts
│ │ │ ├── APICall.tsx
│ │ │ ├── FileDownloadAction.tsx
│ │ │ ├── FileUploadAction.tsx
│ │ │ ├── NavigateAction.tsx
│ │ │ └── TimedAction.tsx
│ │ ├── ApiBoundComponent.tsx
│ │ ├── appContext
│ │ │ ├── date-functions.ts
│ │ │ ├── math-function.ts
│ │ │ └── misc-utils.ts
│ │ ├── AppContext.tsx
│ │ ├── behaviors
│ │ │ ├── Behavior.tsx
│ │ │ └── CoreBehaviors.tsx
│ │ ├── component-hooks.ts
│ │ ├── ComponentDecorator.tsx
│ │ ├── ComponentViewer.tsx
│ │ ├── CompoundComponent.tsx
│ │ ├── constants.ts
│ │ ├── DebugViewProvider.tsx
│ │ ├── descriptorHelper.ts
│ │ ├── devtools
│ │ │ ├── InspectorDialog.module.scss
│ │ │ ├── InspectorDialog.tsx
│ │ │ └── InspectorDialogVisibilityContext.tsx
│ │ ├── EngineError.ts
│ │ ├── event-handlers.ts
│ │ ├── InspectorButton.module.scss
│ │ ├── InspectorContext.tsx
│ │ ├── interception
│ │ │ ├── abstractions.ts
│ │ │ ├── ApiInterceptor.ts
│ │ │ ├── ApiInterceptorProvider.tsx
│ │ │ ├── apiInterceptorWorker.ts
│ │ │ ├── Backend.ts
│ │ │ ├── Errors.ts
│ │ │ ├── IndexedDb.ts
│ │ │ ├── initMock.ts
│ │ │ ├── InMemoryDb.ts
│ │ │ ├── ReadonlyCollection.ts
│ │ │ └── useApiInterceptorContext.tsx
│ │ ├── loader
│ │ │ ├── ApiLoader.tsx
│ │ │ ├── DataLoader.tsx
│ │ │ ├── ExternalDataLoader.tsx
│ │ │ ├── Loader.tsx
│ │ │ ├── MockLoaderRenderer.tsx
│ │ │ └── PageableLoader.tsx
│ │ ├── LoaderComponent.tsx
│ │ ├── markup-check.ts
│ │ ├── parts.ts
│ │ ├── renderers.ts
│ │ ├── rendering
│ │ │ ├── AppContent.tsx
│ │ │ ├── AppRoot.tsx
│ │ │ ├── AppWrapper.tsx
│ │ │ ├── buildProxy.ts
│ │ │ ├── collectFnVarDeps.ts
│ │ │ ├── ComponentAdapter.tsx
│ │ │ ├── ComponentWrapper.tsx
│ │ │ ├── Container.tsx
│ │ │ ├── containers.ts
│ │ │ ├── ContainerWrapper.tsx
│ │ │ ├── ErrorBoundary.module.scss
│ │ │ ├── ErrorBoundary.tsx
│ │ │ ├── InvalidComponent.module.scss
│ │ │ ├── InvalidComponent.tsx
│ │ │ ├── nodeUtils.ts
│ │ │ ├── reducer.ts
│ │ │ ├── renderChild.tsx
│ │ │ ├── StandaloneComponent.tsx
│ │ │ ├── StateContainer.tsx
│ │ │ ├── UnknownComponent.module.scss
│ │ │ ├── UnknownComponent.tsx
│ │ │ └── valueExtractor.ts
│ │ ├── reportEngineError.ts
│ │ ├── RestApiProxy.ts
│ │ ├── script-runner
│ │ │ ├── asyncProxy.ts
│ │ │ ├── AttributeValueParser.ts
│ │ │ ├── bannedFunctions.ts
│ │ │ ├── BindingTreeEvaluationContext.ts
│ │ │ ├── eval-tree-async.ts
│ │ │ ├── eval-tree-common.ts
│ │ │ ├── eval-tree-sync.ts
│ │ │ ├── ParameterParser.ts
│ │ │ ├── process-statement-async.ts
│ │ │ ├── process-statement-common.ts
│ │ │ ├── process-statement-sync.ts
│ │ │ ├── ScriptingSourceTree.ts
│ │ │ ├── simplify-expression.ts
│ │ │ ├── statement-queue.ts
│ │ │ └── visitors.ts
│ │ ├── StandaloneApp.tsx
│ │ ├── StandaloneExtensionManager.ts
│ │ ├── TableOfContentsContext.tsx
│ │ ├── theming
│ │ │ ├── _themes.scss
│ │ │ ├── component-layout-resolver.ts
│ │ │ ├── extendThemeUtils.ts
│ │ │ ├── hvar.ts
│ │ │ ├── layout-resolver.ts
│ │ │ ├── parse-layout-props.ts
│ │ │ ├── StyleContext.tsx
│ │ │ ├── StyleRegistry.ts
│ │ │ ├── ThemeContext.tsx
│ │ │ ├── ThemeProvider.tsx
│ │ │ ├── themes
│ │ │ │ ├── base-utils.ts
│ │ │ │ ├── palette.ts
│ │ │ │ ├── root.ts
│ │ │ │ ├── solid.ts
│ │ │ │ ├── theme-colors.ts
│ │ │ │ └── xmlui.ts
│ │ │ ├── themeVars.module.scss
│ │ │ ├── themeVars.ts
│ │ │ ├── transformThemeVars.ts
│ │ │ └── utils.ts
│ │ ├── utils
│ │ │ ├── actionUtils.ts
│ │ │ ├── audio-utils.ts
│ │ │ ├── base64-utils.ts
│ │ │ ├── compound-utils.ts
│ │ │ ├── css-utils.ts
│ │ │ ├── DataLoaderQueryKeyGenerator.ts
│ │ │ ├── date-utils.ts
│ │ │ ├── extractParam.ts
│ │ │ ├── hooks.tsx
│ │ │ ├── LruCache.ts
│ │ │ ├── mergeProps.ts
│ │ │ ├── misc.ts
│ │ │ ├── request-params.ts
│ │ │ ├── statementUtils.ts
│ │ │ └── treeUtils.ts
│ │ └── xmlui-parser.ts
│ ├── index-standalone.ts
│ ├── index.scss
│ ├── index.ts
│ ├── language-server
│ │ ├── server-common.ts
│ │ ├── server-web-worker.ts
│ │ ├── server.ts
│ │ ├── services
│ │ │ ├── common
│ │ │ │ ├── docs-generation.ts
│ │ │ │ ├── lsp-utils.ts
│ │ │ │ ├── metadata-utils.ts
│ │ │ │ └── syntax-node-utilities.ts
│ │ │ ├── completion.ts
│ │ │ ├── diagnostic.ts
│ │ │ ├── format.ts
│ │ │ └── hover.ts
│ │ └── xmlui-metadata-generated.js
│ ├── logging
│ │ ├── LoggerContext.tsx
│ │ ├── LoggerInitializer.tsx
│ │ ├── LoggerService.ts
│ │ └── xmlui.ts
│ ├── logo.svg
│ ├── parsers
│ │ ├── common
│ │ │ ├── GenericToken.ts
│ │ │ ├── InputStream.ts
│ │ │ └── utils.ts
│ │ ├── scripting
│ │ │ ├── code-behind-collect.ts
│ │ │ ├── Lexer.ts
│ │ │ ├── modules.ts
│ │ │ ├── Parser.ts
│ │ │ ├── ParserError.ts
│ │ │ ├── ScriptingNodeTypes.ts
│ │ │ ├── TokenTrait.ts
│ │ │ ├── TokenType.ts
│ │ │ └── tree-visitor.ts
│ │ ├── style-parser
│ │ │ ├── errors.ts
│ │ │ ├── source-tree.ts
│ │ │ ├── StyleInputStream.ts
│ │ │ ├── StyleLexer.ts
│ │ │ ├── StyleParser.ts
│ │ │ └── tokens.ts
│ │ └── xmlui-parser
│ │ ├── CharacterCodes.ts
│ │ ├── diagnostics.ts
│ │ ├── fileExtensions.ts
│ │ ├── index.ts
│ │ ├── lint.ts
│ │ ├── parser.ts
│ │ ├── ParserError.ts
│ │ ├── scanner.ts
│ │ ├── syntax-kind.ts
│ │ ├── syntax-node.ts
│ │ ├── transform.ts
│ │ ├── utils.ts
│ │ ├── xmlui-serializer.ts
│ │ └── xmlui-tree.ts
│ ├── react-app-env.d.ts
│ ├── syntax
│ │ ├── monaco
│ │ │ ├── grammar.monacoLanguage.ts
│ │ │ ├── index.ts
│ │ │ ├── xmlui-dark.ts
│ │ │ ├── xmlui-light.ts
│ │ │ └── xmluiscript.monacoLanguage.ts
│ │ └── textMate
│ │ ├── index.ts
│ │ ├── xmlui-dark.json
│ │ ├── xmlui-light.json
│ │ ├── xmlui.json
│ │ └── xmlui.tmLanguage.json
│ ├── testing
│ │ ├── assertions.ts
│ │ ├── component-test-helpers.ts
│ │ ├── ComponentDrivers.ts
│ │ ├── drivers
│ │ │ ├── DateInputDriver.ts
│ │ │ ├── index.ts
│ │ │ ├── ModalDialogDriver.ts
│ │ │ ├── NumberBoxDriver.ts
│ │ │ ├── TextBoxDriver.ts
│ │ │ ├── TimeInputDriver.ts
│ │ │ ├── TimerDriver.ts
│ │ │ └── TreeDriver.ts
│ │ ├── fixtures.ts
│ │ ├── index.ts
│ │ ├── infrastructure
│ │ │ ├── index.html
│ │ │ ├── main.tsx
│ │ │ ├── public
│ │ │ │ ├── mockServiceWorker.js
│ │ │ │ ├── resources
│ │ │ │ │ ├── bell.svg
│ │ │ │ │ ├── box.svg
│ │ │ │ │ ├── doc.svg
│ │ │ │ │ ├── eye.svg
│ │ │ │ │ ├── flower-640x480.jpg
│ │ │ │ │ ├── sun.svg
│ │ │ │ │ ├── test-image-100x100.jpg
│ │ │ │ │ └── txt.svg
│ │ │ │ └── serve.json
│ │ │ └── TestBed.tsx
│ │ └── themed-app-test-helpers.ts
│ └── vite-env.d.ts
├── tests
│ ├── components
│ │ ├── CodeBlock
│ │ │ └── hightlight-code.test.ts
│ │ ├── playground-pattern.test.ts
│ │ └── Tree
│ │ └── Tree-states.test.ts
│ ├── components-core
│ │ ├── abstractions
│ │ │ └── treeAbstractions.test.ts
│ │ ├── container
│ │ │ └── buildProxy.test.ts
│ │ ├── interception
│ │ │ ├── orderBy.test.ts
│ │ │ ├── ReadOnlyCollection.test.ts
│ │ │ └── request-param-converter.test.ts
│ │ ├── scripts-runner
│ │ │ ├── AttributeValueParser.test.ts
│ │ │ ├── eval-tree-arrow-async.test.ts
│ │ │ ├── eval-tree-arrow.test.ts
│ │ │ ├── eval-tree-func-decl-async.test.ts
│ │ │ ├── eval-tree-func-decl.test.ts
│ │ │ ├── eval-tree-pre-post.test.ts
│ │ │ ├── eval-tree-regression.test.ts
│ │ │ ├── eval-tree.test.ts
│ │ │ ├── function-proxy.test.ts
│ │ │ ├── parser-regression.test.ts
│ │ │ ├── process-event.test.ts
│ │ │ ├── process-function.test.ts
│ │ │ ├── process-implicit-context.test.ts
│ │ │ ├── process-statement-asgn.test.ts
│ │ │ ├── process-statement-destruct.test.ts
│ │ │ ├── process-statement-regs.test.ts
│ │ │ ├── process-statement-sync.test.ts
│ │ │ ├── process-statement.test.ts
│ │ │ ├── process-switch-sync.test.ts
│ │ │ ├── process-switch.test.ts
│ │ │ ├── process-try-sync.test.ts
│ │ │ ├── process-try.test.ts
│ │ │ └── test-helpers.ts
│ │ ├── test-metadata-handler.ts
│ │ ├── theming
│ │ │ ├── border-segments.test.ts
│ │ │ ├── component-layout.resolver.test.ts
│ │ │ ├── layout-property-parser.test.ts
│ │ │ ├── layout-resolver.test.ts
│ │ │ ├── layout-resolver2.test.ts
│ │ │ ├── layout-vp-override.test.ts
│ │ │ └── padding-segments.test.ts
│ │ └── utils
│ │ ├── date-utils.test.ts
│ │ ├── format-human-elapsed-time.test.ts
│ │ └── LruCache.test.ts
│ ├── language-server
│ │ ├── completion.test.ts
│ │ ├── format.test.ts
│ │ ├── hover.test.ts
│ │ └── mockData.ts
│ └── parsers
│ ├── common
│ │ └── input-stream.test.ts
│ ├── markdown
│ │ └── parse-binding-expression.test.ts
│ ├── parameter-parser.test.ts
│ ├── paremeter-parser.test.ts
│ ├── scripting
│ │ ├── eval-tree-arrow.test.ts
│ │ ├── eval-tree-pre-post.test.ts
│ │ ├── eval-tree.test.ts
│ │ ├── function-proxy.test.ts
│ │ ├── lexer-literals.test.ts
│ │ ├── lexer-misc.test.ts
│ │ ├── module-parse.test.ts
│ │ ├── parser-arrow.test.ts
│ │ ├── parser-assignments.test.ts
│ │ ├── parser-binary.test.ts
│ │ ├── parser-destructuring.test.ts
│ │ ├── parser-errors.test.ts
│ │ ├── parser-expressions.test.ts
│ │ ├── parser-function.test.ts
│ │ ├── parser-literals.test.ts
│ │ ├── parser-primary.test.ts
│ │ ├── parser-regex.test.ts
│ │ ├── parser-statements.test.ts
│ │ ├── parser-unary.test.ts
│ │ ├── process-event.test.ts
│ │ ├── process-implicit-context.test.ts
│ │ ├── process-statement-asgn.test.ts
│ │ ├── process-statement-destruct.test.ts
│ │ ├── process-statement-regs.test.ts
│ │ ├── process-statement-sync.test.ts
│ │ ├── process-statement.test.ts
│ │ ├── process-switch-sync.test.ts
│ │ ├── process-switch.test.ts
│ │ ├── process-try-sync.test.ts
│ │ ├── process-try.test.ts
│ │ ├── simplify-expression.test.ts
│ │ ├── statement-hooks.test.ts
│ │ └── test-helpers.ts
│ ├── style-parser
│ │ ├── generateHvarChain.test.ts
│ │ ├── parseHVar.test.ts
│ │ ├── parser.test.ts
│ │ └── tokens.test.ts
│ └── xmlui
│ ├── lint.test.ts
│ ├── parser.test.ts
│ ├── scanner.test.ts
│ ├── transform.attr.test.ts
│ ├── transform.circular.test.ts
│ ├── transform.element.test.ts
│ ├── transform.errors.test.ts
│ ├── transform.escape.test.ts
│ ├── transform.regression.test.ts
│ ├── transform.script.test.ts
│ ├── transform.test.ts
│ └── xmlui.ts
├── tests-e2e
│ ├── api-bound-component-regression.spec.ts
│ ├── api-call-as-extracted-component.spec.ts
│ ├── assign-to-object-or-array-regression.spec.ts
│ ├── binding-regression.spec.ts
│ ├── children-as-template-context-vars.spec.ts
│ ├── compound-component.spec.ts
│ ├── context-vars-regression.spec.ts
│ ├── data-bindings.spec.ts
│ ├── datasource-and-api-usage-in-var.spec.ts
│ ├── datasource-direct-binding.spec.ts
│ ├── datasource-onLoaded-regression.spec.ts
│ ├── modify-array-item-regression.spec.ts
│ ├── namespaces.spec.ts
│ ├── push-to-array-regression.spec.ts
│ ├── screen-breakpoints.spec.ts
│ ├── scripting.spec.ts
│ ├── state-scope-in-pages.spec.ts
│ └── state-var-scopes.spec.ts
├── tsconfig.json
├── tsdown.config.ts
├── vite.config.ts
└── vitest.config.ts
```
# Files
--------------------------------------------------------------------------------
/docs/content/components/AppState.md:
--------------------------------------------------------------------------------
```markdown
# AppState [#appstate]
`AppState` is an invisible component that provides global state management across your entire application. Unlike component variables that are scoped locally, AppState allows any component to access and update shared state without prop drilling.
**Key advantages over variables:**
- **Global accessibility**: Any component can access the state by referencing the same `bucket`
- **Automatic reactivity**: UI updates automatically when state changes, no manual prop passing required
- **Cross-component coordination**: Perfect for user sessions, UI preferences, loading states, and shared data
## Using AppState [#using-appstate]
Variables in xmlui are a straightforward tool for managing states. However, a variable's scope is the app's main file or the particular component file in which it is declared. To access the variable's value (the stored state), you must pass its value to components wanting to leverage it.
### Storing State in Variables [#storing-state-in-variables]
In the following example, the main file of the app declares a variable, `enhancedMode`, which is toggled with a checkbox:
```xmlui-pg
---app copy display filename="Main.xmlui"
<App var.enhancedMode="{false}">
<VStack gap="$space-4" padding="$space-4">
<Checkbox
label="Enhanced mode"
initialValue="{enhancedMode}"
onDidChange="v => enhancedMode = v" />
<Component1 enhancedMode="{enhancedMode}" />
<Component2 enhancedMode="{enhancedMode}" />
</VStack>
</App>
---desc
Two components, `Component1` and `Component2`, use the value of `enhancedMode`. Because of the aforementioned scoping issue, the app must explicitly pass the variable's value to those components so that they can use it. These components utilize the value to render their UI:
---comp copy display filename="Component1.xmlui"
<Component name="Component1">
<H3 when="{$props.enhancedMode}">I am in enhanced mode!</H3>
<Text when="{!$props.enhancedMode}">Enhanced mode turned off.</Text>
</Component>
---desc
When you define an `AppState`, you can set its `initialValue` property to initialize the state value.
---comp copy display filename="Component2.xmlui"
<Component name="Component2">
<Button enabled="{$props.enhancedMode}">Set enhanced options</Button>
</Component>
---desc
You can try how this app works:
```
### Storing State in AppState [#storing-state-in-appstate]
What if `Component1` and `Component2` had nested components using `enhancedMode`? You must also pass them to the nested components (via properties). What if you have not only one but a dozen of similar properties and a long chain of nested components? The "use a variable" pattern soon becomes a state management nightmare.
If the nested components want to change the state value, you must declare events and event handlers or component APIs to manage the state. It sounds pretty tedious!
This situation is where `AppState` comes into the picture. With an `AppState` instance, you can define a state object that automatically conveys between parent and nested child component chains implicitly.
Let's turn the previous example into one using `AppState`! The following code shows how we change the main app file:
```xmlui-pg
---app copy display filename="Main.xmlui"
<App>
<AppState id="appState" initialValue="{{ enhancedMode: false }}"/>
<VStack gap="$space-4" padding="$space-4">
<Checkbox
label="Enhanced mode"
initialValue="{appState.value.enhancedMode}"
onDidChange="v => appState.update({ enhancedMode: v})" />
<Component1 />
<Component2 />
</VStack>
</App>
---desc
When you define an `AppState`, you can set its `initialValue` property to initialize the state value. You must give an ID to the `AppState` instance to access it and use the `value` property to get the state. You must invoke the `AppState`'s `update` method when you intend to update the state.
The components may use their own `AppState` object to access the state value:
---comp copy display filename="Component1.xmlui"
<Component name="Component1">
<AppState id="state" />
<H3 when="{state.value.enhancedMode}">I am in enhanced mode!</H3>
<Text when="{!state.value.enhancedMode}">Enhanced mode turned off.</Text>
</Component>
---comp copy display filename="Component2.xmlui"
<Component name="Component2">
<AppState id="state" />
<Button enabled="{state.value.enhancedMode}">Set enhanced options</Button>
</Component>
---desc
The modified app works the same way as the previous one (using variables):
```
### State Buckets [#state-buckets]
With the `AppState` component, you can use separate state objects. The `bucket` property of `AppState` is an identifier (using the "default" string by default). While multiple `AppState` objects use the same `bucket` property value, they refer to the same state object.
If you want to run the sample with explicit state buckets (for example, with the `settings` bucket id), you should change the `AppState` declarations accordingly:
```xmlui /bucket="settings"/
<!-- Main.xmlui -->
<AppState id="appState" bucket="settings" initialValue="{{ enhancedMode: false }}"/>
<!-- Component1 -->
<AppState id="state" bucket="settings" />
<!-- Component2 -->
<AppState id="state" bucket="settings" />
```
## Properties [#properties]
### `bucket` (default: "default") [#bucket-default-default]
This property is the identifier of the bucket to which the `AppState` instance is bound. Multiple `AppState` instances with the same bucket will share the same state object: any of them updating the state will cause the other instances to view the new, updated state.
### `initialValue` [#initialvalue]
This property contains the initial state value. Though you can use multiple `AppState`component instances for the same bucket with their `initialValue` set, it may result in faulty app logic. When xmlui instantiates an `AppState` with an explicit initial value, that value is immediately merged with the existing state. The issue may come from the behavior that `initialValue` is set (merged) only when a component mounts. By default, the bucket's initial state is undefined.
## Events [#events]
### `didUpdate` [#didupdate]
This event is fired when the AppState value is updated. The event provides the new state value as its parameter.
## Exposed Methods [#exposed-methods]
### `appendToList` [#appendtolist]
This method appends an item to an array in the application state object bound to the `AppState` instance.
**Signature**: `appendToList(key: string, id: any)`
- `key`: The key of the array in the state object.
- `id`: The item to append to the array.
### `listIncludes` [#listincludes]
This method checks if an array in the application state object contains a specific item.
**Signature**: `listIncludes(key: string, id: any)`
- `key`: The key of the array in the state object.
- `id`: The item to check for in the array.
### `removeFromList` [#removefromlist]
This method removes an item from an array in the application state object bound to the `AppState` instance.
**Signature**: `removeFromList(key: string, id: any)`
- `key`: The key of the array in the state object.
- `id`: The item to remove from the array.
### `update` [#update]
This method updates the application state object bound to the `AppState` instance.
**Signature**: `update(newState: Record<string, any>)`
- `newState`: An object that specifies the new state value.
If the argument is a hash object, it will be merged with the previous state value. Let's assume the previous state value was the following:
```json
{
"enhancedMode": false,
"showHeader": true,
"showFooter": true,
"theme": "light"
}
```
Now, update the state with this call:
```js
appState.update({ enhancedMode: true, theme: "dark" });
```
The new state value will be:
```json
{
"enhancedMode": true,
"showHeader": true,
"showFooter": true,
"theme": "dark"
}
```
## Styling [#styling]
This component does not have any styles.
```
--------------------------------------------------------------------------------
/docs/public/resources/files/for-download/mockApi.js:
--------------------------------------------------------------------------------
```javascript
/* eslint-disable */
/* tslint:disable */
/**
* Mock Service Worker (2.0.1).
* @see https://github.com/mswjs/msw
* - Please do NOT modify this file.
* - Please do NOT serve this file on production.
*/
const INTEGRITY_CHECKSUM = '0877fcdc026242810f5bfde0d7178db4'
const IS_MOCKED_RESPONSE = Symbol('isMockedResponse')
const activeClientIds = new Set()
self.addEventListener('install', function () {
self.skipWaiting()
})
self.addEventListener('activate', function (event) {
event.waitUntil(self.clients.claim())
})
self.addEventListener('message', async function (event) {
const clientId = event.source.id
if (!clientId || !self.clients) {
return
}
const client = await self.clients.get(clientId)
if (!client) {
return
}
const allClients = await self.clients.matchAll({
type: 'window',
})
switch (event.data) {
case 'KEEPALIVE_REQUEST': {
sendToClient(client, {
type: 'KEEPALIVE_RESPONSE',
})
break
}
case 'INTEGRITY_CHECK_REQUEST': {
sendToClient(client, {
type: 'INTEGRITY_CHECK_RESPONSE',
payload: INTEGRITY_CHECKSUM,
})
break
}
case 'MOCK_ACTIVATE': {
activeClientIds.add(clientId)
sendToClient(client, {
type: 'MOCKING_ENABLED',
payload: true,
})
break
}
case 'MOCK_DEACTIVATE': {
activeClientIds.delete(clientId)
break
}
case 'CLIENT_CLOSED': {
activeClientIds.delete(clientId)
const remainingClients = allClients.filter((client) => {
return client.id !== clientId
})
// Unregister itself when there are no more clients
if (remainingClients.length === 0) {
self.registration.unregister()
}
break
}
}
})
self.addEventListener('fetch', function (event) {
const { request } = event
// Bypass navigation requests.
if (request.mode === 'navigate') {
return
}
// Opening the DevTools triggers the "only-if-cached" request
// that cannot be handled by the worker. Bypass such requests.
if (request.cache === 'only-if-cached' && request.mode !== 'same-origin') {
return
}
// Bypass all requests when there are no active clients.
// Prevents the self-unregistered worked from handling requests
// after it's been deleted (still remains active until the next reload).
if (activeClientIds.size === 0) {
return
}
// Generate unique request ID.
const requestId = crypto.randomUUID()
event.respondWith(handleRequest(event, requestId))
})
async function handleRequest(event, requestId) {
const client = await resolveMainClient(event)
const response = await getResponse(event, client, requestId)
// Send back the response clone for the "response:*" life-cycle events.
// Ensure MSW is active and ready to handle the message, otherwise
// this message will pend indefinitely.
if (client && activeClientIds.has(client.id)) {
;(async function () {
const responseClone = response.clone()
// When performing original requests, response body will
// always be a ReadableStream, even for 204 responses.
// But when creating a new Response instance on the client,
// the body for a 204 response must be null.
const responseBody = response.status === 204 ? null : responseClone.body
sendToClient(
client,
{
type: 'RESPONSE',
payload: {
requestId,
isMockedResponse: IS_MOCKED_RESPONSE in response,
type: responseClone.type,
status: responseClone.status,
statusText: responseClone.statusText,
body: responseBody,
headers: Object.fromEntries(responseClone.headers.entries()),
},
},
[responseBody],
)
})()
}
return response
}
// Resolve the main client for the given event.
// Client that issues a request doesn't necessarily equal the client
// that registered the worker. It's with the latter the worker should
// communicate with during the response resolving phase.
async function resolveMainClient(event) {
const client = await self.clients.get(event.clientId)
if (client?.frameType === 'top-level') {
return client
}
const allClients = await self.clients.matchAll({
type: 'window',
})
return allClients
.filter((client) => {
// Get only those clients that are currently visible.
return client.visibilityState === 'visible'
})
.find((client) => {
// Find the client ID that's recorded in the
// set of clients that have registered the worker.
return activeClientIds.has(client.id)
})
}
async function getResponse(event, client, requestId) {
const { request } = event
// Clone the request because it might've been already used
// (i.e. its body has been read and sent to the client).
const requestClone = request.clone()
function passthrough() {
const headers = Object.fromEntries(requestClone.headers.entries())
// Remove internal MSW request header so the passthrough request
// complies with any potential CORS preflight checks on the server.
// Some servers forbid unknown request headers.
delete headers['x-msw-intention']
return fetch(requestClone, { headers })
}
// Bypass mocking when the client is not active.
if (!client) {
return passthrough()
}
// Bypass initial page load requests (i.e. static assets).
// The absence of the immediate/parent client in the map of the active clients
// means that MSW hasn't dispatched the "MOCK_ACTIVATE" event yet
// and is not ready to handle requests.
if (!activeClientIds.has(client.id)) {
return passthrough()
}
// Bypass requests with the explicit bypass header.
// Such requests can be issued by "ctx.fetch()".
const mswIntention = request.headers.get('x-msw-intention')
if (['bypass', 'passthrough'].includes(mswIntention)) {
return passthrough()
}
// Notify the client that a request has been intercepted.
const requestBuffer = await request.arrayBuffer()
const clientMessage = await sendToClient(
client,
{
type: 'REQUEST',
payload: {
id: requestId,
url: request.url,
mode: request.mode,
method: request.method,
headers: Object.fromEntries(request.headers.entries()),
cache: request.cache,
credentials: request.credentials,
destination: request.destination,
integrity: request.integrity,
redirect: request.redirect,
referrer: request.referrer,
referrerPolicy: request.referrerPolicy,
body: requestBuffer,
keepalive: request.keepalive,
},
},
[requestBuffer],
)
switch (clientMessage.type) {
case 'MOCK_RESPONSE': {
return respondWithMock(clientMessage.data)
}
case 'MOCK_NOT_FOUND': {
return passthrough()
}
}
return passthrough()
}
function sendToClient(client, message, transferrables = []) {
return new Promise((resolve, reject) => {
const channel = new MessageChannel()
channel.port1.onmessage = (event) => {
if (event.data && event.data.error) {
return reject(event.data.error)
}
resolve(event.data)
}
client.postMessage(
message,
[channel.port2].concat(transferrables.filter(Boolean)),
)
})
}
async function respondWithMock(response) {
// Setting response status code to 0 is a no-op.
// However, when responding with a "Response.error()", the produced Response
// instance will have status code set to 0. Since it's not possible to create
// a Response instance with status code 0, handle that use-case separately.
if (response.status === 0) {
return Response.error()
}
const mockedResponse = new Response(response.body, response)
Reflect.defineProperty(mockedResponse, IS_MOCKED_RESPONSE, {
value: true,
enumerable: true,
})
return mockedResponse
}
```
--------------------------------------------------------------------------------
/xmlui/tests/parsers/scripting/lexer-misc.test.ts:
--------------------------------------------------------------------------------
```typescript
import { describe, expect, it } from "vitest";
import { Lexer } from "../../../src/parsers/scripting/Lexer";
import { InputStream } from "../../../src/parsers/common/InputStream";
import { TokenType } from "../../../src/parsers/scripting/TokenType";
describe("Lexer - miscellaneous", () => {
it("Empty", () => {
// --- Arrange
const source = "";
const wLexer = new Lexer(new InputStream(source));
// --- Act
const next = wLexer.get();
// --- Assert
expect(next.type).equal(TokenType.Eof);
expect(next.text).equal(source);
expect(next.startPosition).equal(0);
expect(next.endPosition).equal(source.length);
expect(next.startLine).equal(1);
expect(next.endLine).equal(1);
expect(next.startColumn).equal(0);
expect(next.endColumn).equal(source.length);
});
const miscCases = [
{ src: "...", exp: TokenType.Spread },
{ src: ";", exp: TokenType.Semicolon },
{ src: "/", exp: TokenType.Divide },
{ src: "**", exp: TokenType.Exponent },
{ src: "*", exp: TokenType.Multiply },
{ src: "%", exp: TokenType.Remainder },
{ src: "+", exp: TokenType.Plus },
{ src: "-", exp: TokenType.Minus },
{ src: "^", exp: TokenType.BitwiseXor },
{ src: "|", exp: TokenType.BitwiseOr },
{ src: "||", exp: TokenType.LogicalOr },
{ src: "&", exp: TokenType.BitwiseAnd },
{ src: "&&", exp: TokenType.LogicalAnd },
{ src: ",", exp: TokenType.Comma },
{ src: "(", exp: TokenType.LParent },
{ src: ")", exp: TokenType.RParent },
{ src: ":", exp: TokenType.Colon },
{ src: "[", exp: TokenType.LSquare },
{ src: "]", exp: TokenType.RSquare },
{ src: "?", exp: TokenType.QuestionMark },
{ src: "??", exp: TokenType.NullCoalesce },
{ src: "?.", exp: TokenType.OptionalChaining },
{ src: "{", exp: TokenType.LBrace },
{ src: "}", exp: TokenType.RBrace },
{ src: "=", exp: TokenType.Assignment },
{ src: "==", exp: TokenType.Equal },
{ src: "===", exp: TokenType.StrictEqual },
{ src: "!", exp: TokenType.LogicalNot },
{ src: "!=", exp: TokenType.NotEqual },
{ src: "!==", exp: TokenType.StrictNotEqual },
{ src: "<", exp: TokenType.LessThan },
{ src: "<=", exp: TokenType.LessThanOrEqual },
{ src: "<<", exp: TokenType.ShiftLeft },
{ src: ">", exp: TokenType.GreaterThan },
{ src: ">=", exp: TokenType.GreaterThanOrEqual },
{ src: ">>", exp: TokenType.SignedShiftRight },
{ src: ">>>", exp: TokenType.ShiftRight },
{ src: ".", exp: TokenType.Dot },
{ src: "thisId", exp: TokenType.Identifier },
{ src: "_other145$", exp: TokenType.Identifier },
{ src: "$loader", exp: TokenType.Identifier },
{ src: "Infinity", exp: TokenType.Infinity },
{ src: "NaN", exp: TokenType.NaN },
{ src: "true", exp: TokenType.True },
{ src: "false", exp: TokenType.False },
{ src: "$item", exp: TokenType.Identifier },
{ src: "null", exp: TokenType.Null },
{ src: "undefined", exp: TokenType.Undefined },
{ src: "in", exp: TokenType.In },
{ src: "+=", exp: TokenType.AddAssignment },
{ src: "-=", exp: TokenType.SubtractAssignment },
{ src: "**=", exp: TokenType.ExponentAssignment },
{ src: "*=", exp: TokenType.MultiplyAssignment },
{ src: "/=", exp: TokenType.DivideAssignment },
{ src: "%=", exp: TokenType.RemainderAssignment },
{ src: "<<=", exp: TokenType.ShiftLeftAssignment },
{ src: ">>=", exp: TokenType.SignedShiftRightAssignment },
{ src: ">>>=", exp: TokenType.ShiftRightAssignment },
{ src: "&=", exp: TokenType.BitwiseAndAssignment },
{ src: "&&=", exp: TokenType.LogicalAndAssignment },
{ src: "^=", exp: TokenType.BitwiseXorAssignment },
{ src: "|=", exp: TokenType.BitwiseOrAssignment },
{ src: "||=", exp: TokenType.LogicalOrAssignment },
{ src: "??=", exp: TokenType.NullCoalesceAssignment },
{ src: "=>", exp: TokenType.Arrow },
{ src: "++", exp: TokenType.IncOp },
{ src: "--", exp: TokenType.DecOp },
{ src: "let", exp: TokenType.Let },
{ src: "const", exp: TokenType.Const },
{ src: "var", exp: TokenType.Var },
{ src: "if", exp: TokenType.If },
{ src: "else", exp: TokenType.Else },
{ src: "return", exp: TokenType.Return },
{ src: "break", exp: TokenType.Break },
{ src: "continue", exp: TokenType.Continue },
{ src: "do", exp: TokenType.Do },
{ src: "while", exp: TokenType.While },
{ src: "for", exp: TokenType.For },
{ src: "of", exp: TokenType.Of },
{ src: "try", exp: TokenType.Try },
{ src: "catch", exp: TokenType.Catch },
{ src: "finally", exp: TokenType.Finally },
{ src: "throw", exp: TokenType.Throw },
{ src: "switch", exp: TokenType.Switch },
{ src: "case", exp: TokenType.Case },
{ src: "default", exp: TokenType.Default },
{ src: "delete", exp: TokenType.Delete },
{ src: "function", exp: TokenType.Function },
{ src: "as", exp: TokenType.As },
];
miscCases.forEach(c => {
it(`Token ${c.src} #1`, () => {
const source = c.src;
const wLexer = new Lexer(new InputStream(source));
// --- Act
const next = wLexer.get();
// --- Assert
expect(next.type).equal(c.exp);
expect(next.text).equal(source);
expect(next.startPosition).equal(0);
expect(next.endPosition).equal(source.length);
expect(next.startLine).equal(1);
expect(next.endLine).equal(1);
expect(next.startColumn).equal(0);
expect(next.endColumn).equal(source.length);
});
it(`Token ${c.src} #2`, () => {
const source = ` \t \r ${c.src}`;
const wLexer = new Lexer(new InputStream(source));
// --- Act
const next = wLexer.get();
// --- Assert
expect(next.type).equal(c.exp);
expect(next.text).equal(c.src);
expect(next.startPosition).equal(5);
expect(next.endPosition).equal(source.length);
expect(next.startLine).equal(1);
expect(next.endLine).equal(1);
expect(next.startColumn).equal(5);
expect(next.endColumn).equal(source.length);
});
it(`Token ${c.src} #3`, () => {
const source = ` /* c */ ${c.src}`;
const wLexer = new Lexer(new InputStream(source));
// --- Act
const next = wLexer.get();
// --- Assert
expect(next.type).equal(c.exp);
expect(next.text).equal(c.src);
expect(next.startPosition).equal(9);
expect(next.endPosition).equal(source.length);
expect(next.startLine).equal(1);
expect(next.endLine).equal(1);
expect(next.startColumn).equal(9);
expect(next.endColumn).equal(source.length);
});
it(`Token ${c.src} #4`, () => {
const source = `${c.src} \t \r `;
const wLexer = new Lexer(new InputStream(source));
// --- Act
const next = wLexer.get();
// --- Assert
expect(next.type).equal(c.exp);
expect(next.text).equal(c.src);
expect(next.startPosition).equal(0);
expect(next.endPosition).equal(c.src.length);
expect(next.startLine).equal(1);
expect(next.endLine).equal(1);
expect(next.startColumn).equal(0);
expect(next.endColumn).equal(c.src.length);
});
it(`Token ${c.src} #5`, () => {
const source = `${c.src} // c`;
const wLexer = new Lexer(new InputStream(source));
// --- Act
const next = wLexer.get();
const trail1 = wLexer.get(true);
const trail2 = wLexer.get(true);
const trail3 = wLexer.get();
// --- Assert
expect(next.type).equal(c.exp);
expect(next.text).equal(c.src);
expect(next.startPosition).equal(0);
expect(next.endPosition).equal(c.src.length);
expect(next.startLine).equal(1);
expect(next.endLine).equal(1);
expect(next.startColumn).equal(0);
expect(next.endColumn).equal(c.src.length);
expect(trail1.type).equal(TokenType.Ws);
expect(trail2.type).equal(TokenType.EolComment);
expect(trail3.type).equal(TokenType.Eof);
});
});
});
```
--------------------------------------------------------------------------------
/xmlui/tests/components-core/theming/layout-resolver2.test.ts:
--------------------------------------------------------------------------------
```typescript
import { describe, expect, it } from "vitest";
import { resolveLayoutProps, toCssVar } from "../../../src/components-core/theming/layout-resolver";
describe("Layout resolver 2", () => {
const THEME_ID = "$some-theme-id_x";
const THEME_ID_VALUE = toCssVar(THEME_ID);
const THEME_ID2 = "$some-theme-id2";
const THEME_ID2_VALUE = toCssVar(THEME_ID2);
const THEME_ID3 = "$some-theme-id3";
const THEME_ID3_VALUE = toCssVar(THEME_ID3);
const THEME_ID4 = "$some-theme-id4";
const THEME_ID4_VALUE = toCssVar(THEME_ID4);
// --- Borders
it("textDecoration", () => {
const PROP = "textDecoration";
const VALUE = "underline solid red";
const result = resolveLayoutProps({ [PROP]: VALUE });
expect(result.cssProps[PROP]).toBe(VALUE);
expect(result.issues.has(PROP)).toBe(false);
});
it("textDecorationLine", () => {
const PROP = "textDecorationLine";
const VALUE = "overline";
const result = resolveLayoutProps({ [PROP]: VALUE });
expect(result.cssProps[PROP]).toBe(VALUE);
expect(result.issues.has(PROP)).toBe(false);
});
it("textDecorationColor", () => {
const PROP = "textDecorationColor";
const VALUE = "blue";
const result = resolveLayoutProps({ [PROP]: VALUE });
expect(result.cssProps[PROP]).toBe(VALUE);
expect(result.issues.has(PROP)).toBe(false);
});
it("textDecorationStyle", () => {
const PROP = "textDecorationStyle";
const VALUE = "dotted";
const result = resolveLayoutProps({ [PROP]: VALUE });
expect(result.cssProps[PROP]).toBe(VALUE);
expect(result.issues.has(PROP)).toBe(false);
});
it("textDecorationThickness", () => {
const PROP = "textDecorationThickness";
const VALUE = "8px";
const result = resolveLayoutProps({ [PROP]: VALUE });
expect(result.cssProps[PROP]).toBe(VALUE);
expect(result.issues.has(PROP)).toBe(false);
});
it("textUnderlineOffset", () => {
const PROP = "textUnderlineOffset";
const VALUE = "8px";
const result = resolveLayoutProps({ [PROP]: VALUE });
expect(result.cssProps[PROP]).toBe(VALUE);
expect(result.issues.has(PROP)).toBe(false);
});
it("outline", () => {
const PROP = "outline";
const VALUE = "3px solid green";
const result = resolveLayoutProps({ [PROP]: VALUE });
expect(result.cssProps[PROP]).toBe(VALUE);
expect(result.issues.has(PROP)).toBe(false);
});
it("outlineWidth", () => {
const PROP = "outlineWidth";
const VALUE = "3px";
const result = resolveLayoutProps({ [PROP]: VALUE });
expect(result.cssProps[PROP]).toBe(VALUE);
expect(result.issues.has(PROP)).toBe(false);
});
it("outlineColor", () => {
const PROP = "outlineColor";
const VALUE = "green";
const result = resolveLayoutProps({ [PROP]: VALUE });
expect(result.cssProps[PROP]).toBe(VALUE);
expect(result.issues.has(PROP)).toBe(false);
});
it("outlineStyle", () => {
const PROP = "outlineStyle";
const VALUE = "dotted";
const result = resolveLayoutProps({ [PROP]: VALUE });
expect(result.cssProps[PROP]).toBe(VALUE);
expect(result.issues.has(PROP)).toBe(false);
});
it("outlineOffset", () => {
const PROP = "outlineOffset";
const VALUE = "5px";
const result = resolveLayoutProps({ [PROP]: VALUE });
expect(result.cssProps[PROP]).toBe(VALUE);
expect(result.issues.has(PROP)).toBe(false);
});
it("borderHorizontal: 1px solid red", () => {
const PROP = "borderHorizontal";
const VALUE = "1px solid red";
const result = resolveLayoutProps({ [PROP]: VALUE });
expect(result.cssProps.borderLeft).toBe(VALUE);
expect(result.cssProps.borderRight).toBe(VALUE);
expect(result.issues.has(PROP)).toBe(false);
});
it("borderLeft overwrites borderHorizontal", () => {
const PROP = "borderHorizontal";
const VALUE = "1px solid red";
const result = resolveLayoutProps({ [PROP]: VALUE, borderLeft: "2px solid blue" });
expect(result.cssProps.borderLeft).toBe("2px solid blue");
expect(result.cssProps.borderRight).toBe(VALUE);
expect(result.issues.has(PROP)).toBe(false);
});
it("borderRight overwrites borderHorizontal", () => {
const PROP = "borderHorizontal";
const VALUE = "1px solid red";
const result = resolveLayoutProps({ [PROP]: VALUE, borderRight: "2px solid blue" });
expect(result.cssProps.borderLeft).toBe(VALUE);
expect(result.cssProps.borderRight).toBe("2px solid blue");
expect(result.issues.has(PROP)).toBe(false);
});
it("borderHorizontal (themeVar) 1", () => {
const PROP = "borderHorizontal";
const VALUE = THEME_ID;
const result = resolveLayoutProps({ [PROP]: VALUE });
expect(result.cssProps.borderLeft).toBe(THEME_ID_VALUE);
expect(result.cssProps.borderRight).toBe(THEME_ID_VALUE);
expect(result.issues.has(PROP)).toBe(false);
});
it("borderHorizontal (themeVar) 2", () => {
const PROP = "borderHorizontal";
const VALUE = `${THEME_ID} ${THEME_ID2}`;
const result = resolveLayoutProps({ [PROP]: VALUE });
expect(result.cssProps.borderLeft).toBe(`${THEME_ID_VALUE} ${THEME_ID2_VALUE}`);
expect(result.cssProps.borderRight).toBe(`${THEME_ID_VALUE} ${THEME_ID2_VALUE}`);
expect(result.issues.has(PROP)).toBe(false);
});
it("borderHorizontal (themeVar) 3", () => {
const PROP = "borderHorizontal";
const VALUE = `${THEME_ID} ${THEME_ID2} ${THEME_ID3}`;
const result = resolveLayoutProps({ [PROP]: VALUE });
expect(result.cssProps.borderLeft).toBe(`${THEME_ID_VALUE} ${THEME_ID2_VALUE} ${THEME_ID3_VALUE}`);
expect(result.cssProps.borderRight).toBe(`${THEME_ID_VALUE} ${THEME_ID2_VALUE} ${THEME_ID3_VALUE}`);
expect(result.issues.has(PROP)).toBe(false);
});
it("borderVertical: 1px solid red", () => {
const PROP = "borderVertical";
const VALUE = "1px solid red";
const result = resolveLayoutProps({ [PROP]: VALUE });
expect(result.cssProps.borderTop).toBe(VALUE);
expect(result.cssProps.borderBottom).toBe(VALUE);
expect(result.issues.has(PROP)).toBe(false);
});
it("borderVertical (themeVar) 1", () => {
const PROP = "borderVertical";
const VALUE = THEME_ID;
const result = resolveLayoutProps({ [PROP]: VALUE });
expect(result.cssProps.borderTop).toBe(THEME_ID_VALUE);
expect(result.cssProps.borderBottom).toBe(THEME_ID_VALUE);
expect(result.issues.has(PROP)).toBe(false);
});
it("borderVertical (themeVar) 2", () => {
const PROP = "borderVertical";
const VALUE = `${THEME_ID} ${THEME_ID2}`;
const result = resolveLayoutProps({ [PROP]: VALUE });
expect(result.cssProps.borderTop).toBe(`${THEME_ID_VALUE} ${THEME_ID2_VALUE}`);
expect(result.cssProps.borderBottom).toBe(`${THEME_ID_VALUE} ${THEME_ID2_VALUE}`);
expect(result.issues.has(PROP)).toBe(false);
});
it("borderVertical (themeVar) 3", () => {
const PROP = "borderVertical";
const VALUE = `${THEME_ID} ${THEME_ID2} ${THEME_ID3}`;
const result = resolveLayoutProps({ [PROP]: VALUE });
expect(result.cssProps.borderTop).toBe(`${THEME_ID_VALUE} ${THEME_ID2_VALUE} ${THEME_ID3_VALUE}`);
expect(result.cssProps.borderBottom).toBe(`${THEME_ID_VALUE} ${THEME_ID2_VALUE} ${THEME_ID3_VALUE}`);
expect(result.issues.has(PROP)).toBe(false);
});
it("borderTop overwrites borderVertical", () => {
const PROP = "borderVertical";
const VALUE = "1px solid red";
const result = resolveLayoutProps({ [PROP]: VALUE, borderTop: "2px solid blue" });
expect(result.cssProps.borderTop).toBe("2px solid blue");
expect(result.cssProps.borderBottom).toBe(VALUE);
expect(result.issues.has(PROP)).toBe(false);
});
it("borderBottom overwrites borderVertical", () => {
const PROP = "borderVertical";
const VALUE = "1px solid red";
const result = resolveLayoutProps({ [PROP]: VALUE, borderBottom: "2px solid blue" });
expect(result.cssProps.borderTop).toBe(VALUE);
expect(result.cssProps.borderBottom).toBe("2px solid blue");
expect(result.issues.has(PROP)).toBe(false);
});
});
```
--------------------------------------------------------------------------------
/xmlui/src/components-core/interception/IndexedDb.ts:
--------------------------------------------------------------------------------
```typescript
import type { Table } from "dexie";
import Dexie from "dexie";
import { ReadOnlyCollection } from "../interception/ReadonlyCollection";
import type { IDatabase, TableDescriptor } from "./abstractions";
export class IndexedDb implements IDatabase {
// private repository: Record<string, Array<any>>;
// private maxIdsByCollections: Record<string, number> = {};
private db: Dexie | null = null;
[key: string]: unknown;
constructor(
private tables: Array<TableDescriptor> | undefined,
private initialData: Record<string, any[]> | (() => Promise<Record<string, any[]>>) = {},
private config?: any,
) {}
private getDb() {
if (this.db === null) {
throw new Error("Db is not initialized yet");
}
return this.db;
}
public async initialize() {
const resolvedInitialData =
typeof this.initialData === "function" ? await this.initialData() : this.initialData;
const schema: Record<string, string> = {};
const tableNames = new Set<string>();
if (this.tables) {
this.tables.forEach((tableDescriptor) => {
const schemaArray = [];
if (tableDescriptor.pk.length === 1) {
schemaArray.push(tableDescriptor.pk[0]);
} else {
schemaArray.push(`[${tableDescriptor.pk.join("+")}]`); //indexeddb compound index looks like this: [field+anotherfield]
}
if (tableDescriptor.indexes) {
schemaArray.push(...tableDescriptor.indexes);
}
schema[tableDescriptor.name] = schemaArray.join(", ");
tableNames.add(tableDescriptor.name);
});
} else {
Object.entries(resolvedInitialData).forEach(([key]) => {
schema[key] = "++id";
tableNames.add(key);
});
}
const targetVersion =
this.config?.version !== undefined && typeof this.config?.version === "number"
? this.config?.version
: 1;
const shouldInitializeData = await this.dropDbOnVersionChange(targetVersion);
this.db = this.createDbInstance();
this.db.version(targetVersion).stores(schema);
if (shouldInitializeData) {
await Promise.all(
Object.entries(resolvedInitialData).map(async ([key, value]) => {
try {
await this.getDb().table(key).bulkAdd(value);
} catch (ignored) {
console.error(ignored);
}
}),
);
}
tableNames.forEach((key) => {
this[`$${key}`] = createTableWrapper(this.getDb().table(key));
});
}
private createDbInstance() {
return new Dexie(this.config?.database ?? "defaultIndexDb");
}
private async dropDbOnVersionChange(targetVersion: number) {
const tempDb = this.createDbInstance();
if (!(await Dexie.exists(tempDb.name))) {
return true;
}
await tempDb.open();
if (tempDb.verno !== targetVersion) {
await tempDb.delete();
return true;
} else {
tempDb.close();
return false;
}
}
public getItems = (resourceId: string) => this.getDb().table(resourceId).toArray();
public findItems = async (resourceId: string, predicate: (item: any) => Promise<boolean>) => {
const ret = await this.getItems(resourceId);
const results = await Promise.all(ret.map(predicate));
return ret.filter((_v, index) => results[index]);
};
public getItem = async (resourceId: string, predicate: (item: any) => Promise<boolean>) => {
const ret = await this.getItems(resourceId);
const results = await Promise.all(ret.map(predicate));
return ret.find((_v, index) => results[index]);
};
public getItemById = async (resourceId: string, id: any) => {
return await this.getItem(resourceId, (item) => {
return Promise.resolve(item.id + "" === id + "");
});
};
public deleteItems = async (resourceId: string, predicate: (item: any) => Promise<boolean>) => {
// this.repository[resourceId] = this.repository[resourceId]?.filter((item) => !predicate(item));
};
public insertItem = async (resourceId: string, item: any) => {
const id = await this.getDb().table(resourceId).add(item);
return this.getItemById(resourceId, id);
};
public updateItem = async (resourceId: string, item: any) => {
await this.getDb().table(resourceId).update(item.id, item);
return await this.getItemById(resourceId, item.id);
};
// --- This method signifies that a section of operations is executed in a transaction
async transaction(actions: () => Promise<void>): Promise<void> {
if (!this.db) return;
const tables = this.db.tables;
await this.db.transaction("rw", tables, actions);
}
}
// Wraps an indexDb Table into an object that provides helpful methods
function createTableWrapper(table: Table): any {
// --- Function to retrieve the current table data
const getDataFn = () => table.db.table(table.name);
// --- Helper method to filter the table data
const filteredData = async (predicate?: (item: any) => Promise<boolean>) => {
const dataSnapshot = await table.toArray();
const results = await Promise.all(dataSnapshot.map(predicate ?? (() => Promise.resolve(true))));
return dataSnapshot.filter((_v, index) => results[index]);
};
return {
native: getDataFn,
insert: async (item: any) => {
const id = await table.add(item);
return getDataFn().get(id);
},
update: async (item: any) => {
await table.update(item.id, item);
return getDataFn().get(item.id);
},
save: async (item: any) => {
const key = await table.put(item);
return table.get(key);
},
deleteById: async (id: any) => {
await table.delete(id);
},
byId: async (id: any) => {
if (id === undefined || id === null) {
return null;
}
let safeId = id;
if (table.schema.primKey.src === "++id") {
//it's an auto incremented id, must be a number
safeId = Number(id);
}
return await table.get(safeId);
},
toArray: async () => await table.toArray(),
single: async (predicate: (item: any) => Promise<boolean>) =>
await new ReadOnlyCollection(await table.toArray()).single(predicate),
singleOrDefault: async (predicate: (item: any) => Promise<boolean>, defValue?: any) => {
return await new ReadOnlyCollection(await table.toArray()).singleOrDefault(
predicate,
defValue,
);
},
where: async (predicate: (item: any) => Promise<boolean>) =>
new ReadOnlyCollection(await filteredData(predicate)),
whereAsArray: async (predicate: (item: any) => Promise<boolean>) =>
await filteredData(predicate),
orderBy: async (...mappers: any[]) =>
await new ReadOnlyCollection(await table.toArray()).orderBy(...mappers),
orderByAsArray: async (...mappers: any[]) =>
await new ReadOnlyCollection(await table.toArray()).orderByAsArray(...mappers),
groupBy: async (groupKey: (item: any) => Promise<any>) =>
await new ReadOnlyCollection(await table.toArray()).groupBy(groupKey),
groupByAsArray: async (groupKey: (item: any) => Promise<any>) =>
await new ReadOnlyCollection(await table.toArray()).groupByAsArray(groupKey),
distinct: async (distinctValue?: (item: any) => Promise<any>) =>
await new ReadOnlyCollection(await table.toArray()).distinct(distinctValue),
distinctAsArray: async (distinctValue?: (item: any) => Promise<any>) =>
await new ReadOnlyCollection(await table.toArray()).distinctAsArray(distinctValue),
maxValue: async (fieldName = "id", predicate?: (item: any) => Promise<boolean>) => {
return await new ReadOnlyCollection(await table.toArray()).maxValue(fieldName, predicate);
},
skip: async (count: number) => {
return await new ReadOnlyCollection(await table.toArray()).skip(count);
},
take: async (count: number) => {
return await new ReadOnlyCollection(await table.toArray()).take(count);
},
skipTake: async (skip: number, take: number) => {
return await new ReadOnlyCollection(await table.toArray()).skipTake(skip, take);
},
};
}
```
--------------------------------------------------------------------------------
/xmlui/src/components/IconRegistryContext.tsx:
--------------------------------------------------------------------------------
```typescript
import React, { useCallback, useContext } from "react";
import type { IconRegistry } from "./IconProvider";
import { useIsomorphicLayoutEffect } from "../components-core/utils/hooks";
export const IconRegistryContext = React.createContext<IconRegistry | null>(null);
export function useIconRegistry() {
return useContext(IconRegistryContext)!;
}
//https://github.com/hatashiro/react-attr-converter/tree/master
const svgAttributeMap: Record<string, string> = {
// SVG attributes
accentheight: "accentHeight",
accumulate: "accumulate",
additive: "additive",
alignmentbaseline: "alignmentBaseline",
allowreorder: "allowReorder",
alphabetic: "alphabetic",
amplitude: "amplitude",
arabicform: "arabicForm",
ascent: "ascent",
attributename: "attributeName",
attributetype: "attributeType",
autoreverse: "autoReverse",
azimuth: "azimuth",
basefrequency: "baseFrequency",
baseprofile: "baseProfile",
baselineshift: "baselineShift",
bbox: "bbox",
begin: "begin",
bias: "bias",
by: "by",
calcmode: "calcMode",
capheight: "capHeight",
clip: "clip",
clippath: "clipPath",
clippathunits: "clipPathUnits",
cliprule: "clipRule",
colorinterpolation: "colorInterpolation",
colorinterpolationfilters: "colorInterpolationFilters",
colorprofile: "colorProfile",
colorrendering: "colorRendering",
contentscripttype: "contentScriptType",
contentstyletype: "contentStyleType",
cursor: "cursor",
cx: "cx",
cy: "cy",
d: "d",
decelerate: "decelerate",
descent: "descent",
diffuseconstant: "diffuseConstant",
direction: "direction",
display: "display",
divisor: "divisor",
dominantbaseline: "dominantBaseline",
dur: "dur",
dx: "dx",
dy: "dy",
edgemode: "edgeMode",
elevation: "elevation",
enablebackground: "enableBackground",
end: "end",
exponent: "exponent",
externalresourcesrequired: "externalResourcesRequired",
fill: "fill",
fillopacity: "fillOpacity",
fillrule: "fillRule",
filter: "filter",
filterres: "filterRes",
filterunits: "filterUnits",
floodcolor: "floodColor",
floodopacity: "floodOpacity",
focusable: "focusable",
fontfamily: "fontFamily",
fontsize: "fontSize",
fontsizeadjust: "fontSizeAdjust",
fontstretch: "fontStretch",
fontstyle: "fontStyle",
fontvariant: "fontVariant",
fontweight: "fontWeight",
format: "format",
from: "from",
fx: "fx",
fy: "fy",
g1: "g1",
g2: "g2",
glyphname: "glyphName",
glyphorientationhorizontal: "glyphOrientationHorizontal",
glyphorientationvertical: "glyphOrientationVertical",
glyphref: "glyphRef",
gradienttransform: "gradientTransform",
gradientunits: "gradientUnits",
hanging: "hanging",
horizadvx: "horizAdvX",
horizoriginx: "horizOriginX",
ideographic: "ideographic",
imagerendering: "imageRendering",
in: "in",
in2: "in2",
intercept: "intercept",
k: "k",
k1: "k1",
k2: "k2",
k3: "k3",
k4: "k4",
kernelmatrix: "kernelMatrix",
kernelunitlength: "kernelUnitLength",
kerning: "kerning",
keypoints: "keyPoints",
keysplines: "keySplines",
keytimes: "keyTimes",
lengthadjust: "lengthAdjust",
letterspacing: "letterSpacing",
lightingcolor: "lightingColor",
limitingconeangle: "limitingConeAngle",
local: "local",
markerend: "markerEnd",
markerheight: "markerHeight",
markermid: "markerMid",
markerstart: "markerStart",
markerunits: "markerUnits",
markerwidth: "markerWidth",
mask: "mask",
maskcontentunits: "maskContentUnits",
maskunits: "maskUnits",
mathematical: "mathematical",
mode: "mode",
numoctaves: "numOctaves",
offset: "offset",
opacity: "opacity",
operator: "operator",
order: "order",
orient: "orient",
orientation: "orientation",
origin: "origin",
overflow: "overflow",
overlineposition: "overlinePosition",
overlinethickness: "overlineThickness",
paintorder: "paintOrder",
panose1: "panose1",
pathlength: "pathLength",
patterncontentunits: "patternContentUnits",
patterntransform: "patternTransform",
patternunits: "patternUnits",
pointerevents: "pointerEvents",
points: "points",
pointsatx: "pointsAtX",
pointsaty: "pointsAtY",
pointsatz: "pointsAtZ",
preservealpha: "preserveAlpha",
preserveaspectratio: "preserveAspectRatio",
primitiveunits: "primitiveUnits",
r: "r",
radius: "radius",
refx: "refX",
refy: "refY",
renderingintent: "renderingIntent",
repeatcount: "repeatCount",
repeatdur: "repeatDur",
requiredextensions: "requiredExtensions",
requiredfeatures: "requiredFeatures",
restart: "restart",
result: "result",
rotate: "rotate",
rx: "rx",
ry: "ry",
scale: "scale",
seed: "seed",
shaperendering: "shapeRendering",
slope: "slope",
spacing: "spacing",
specularconstant: "specularConstant",
specularexponent: "specularExponent",
speed: "speed",
spreadmethod: "spreadMethod",
startoffset: "startOffset",
stddeviation: "stdDeviation",
stemh: "stemh",
stemv: "stemv",
stitchtiles: "stitchTiles",
stopcolor: "stopColor",
stopopacity: "stopOpacity",
strikethroughposition: "strikethroughPosition",
strikethroughthickness: "strikethroughThickness",
string: "string",
stroke: "stroke",
strokedasharray: "strokeDasharray",
strokedashoffset: "strokeDashoffset",
strokelinecap: "strokeLinecap",
strokelinejoin: "strokeLinejoin",
strokemiterlimit: "strokeMiterlimit",
strokeopacity: "strokeOpacity",
strokewidth: "strokeWidth",
surfacescale: "surfaceScale",
systemlanguage: "systemLanguage",
tablevalues: "tableValues",
targetx: "targetX",
targety: "targetY",
textanchor: "textAnchor",
textdecoration: "textDecoration",
textlength: "textLength",
textrendering: "textRendering",
to: "to",
transform: "transform",
u1: "u1",
u2: "u2",
underlineposition: "underlinePosition",
underlinethickness: "underlineThickness",
unicode: "unicode",
unicodebidi: "unicodeBidi",
unicoderange: "unicodeRange",
unitsperem: "unitsPerEm",
valphabetic: "vAlphabetic",
vhanging: "vHanging",
videographic: "vIdeographic",
vmathematical: "vMathematical",
values: "values",
vectoreffect: "vectorEffect",
version: "version",
vertadvy: "vertAdvY",
vertoriginx: "vertOriginX",
vertoriginy: "vertOriginY",
viewbox: "viewBox",
viewtarget: "viewTarget",
visibility: "visibility",
widths: "widths",
wordspacing: "wordSpacing",
writingmode: "writingMode",
x: "x",
x1: "x1",
x2: "x2",
xchannelselector: "xChannelSelector",
xheight: "xHeight",
xlinkactuate: "xlinkActuate",
xlinkarcrole: "xlinkArcrole",
xlinkhref: "xlinkHref",
xlinkrole: "xlinkRole",
xlinkshow: "xlinkShow",
xlinktitle: "xlinkTitle",
xlinktype: "xlinkType",
xmlns: "xmlns",
xmlnsxlink: "xmlnsXlink",
xmlbase: "xmlBase",
xmllang: "xmlLang",
xmlspace: "xmlSpace",
y: "y",
y1: "y1",
y2: "y2",
ychannelselector: "yChannelSelector",
z: "z",
zoomandpan: "zoomAndPan",
};
const extraCharRegex = /[-:]/g;
export function useCustomSvgIconRenderer(resourceUrl?: string) {
const { ensureCustomSvgIcon, customSvgs } = useIconRegistry();
useIsomorphicLayoutEffect(() => {
if (!resourceUrl) {
return;
}
void ensureCustomSvgIcon(resourceUrl);
}, [ensureCustomSvgIcon, resourceUrl]);
const customSvg = resourceUrl ? customSvgs[resourceUrl] : null;
const iconRenderer = useCallback(
({ style, className }: any) => {
if (!customSvg) {
return null;
}
const { attributes, name } = customSvg;
const safeAttributes: any = {};
Object.entries(attributes).forEach(([key, value]) => {
let safeKey = key;
if (/^(data-|aria-)/.test(key)) {
safeKey = key;
} else {
safeKey = key.replace(extraCharRegex, "").toLowerCase();
}
safeAttributes[svgAttributeMap[safeKey] || key] = value;
});
return (
<svg {...safeAttributes} style={style} className={className}>
<use href={`#${name}`} />
</svg>
);
},
[customSvg],
);
return !resourceUrl ? null : iconRenderer;
}
```
--------------------------------------------------------------------------------
/xmlui/src/components/Select/Select.md:
--------------------------------------------------------------------------------
```markdown
%-DESC-START
**Key features:**
- **Flexible selection modes**: Single selection by default, with optional multi-select capability
- **Option containers**: Uses Option components to define selectable items with separate values and labels
- **Search functionality**: Optional filtering to quickly find options in large lists
- **Custom templates**: Configurable option display, value presentation, and empty state templates
- **Dynamic options**: Supports both static [Option](/components/Option) children and dynamic lists via [Items](/components/Items).
## Using `Select`
The component accepts `Option` components as children defining a particular option's label-value pair.
`Option` requires a `value` property and while also having a `label` that is displayed in the list.
If the `label` is not specified `value` is shown.
```xmlui-pg copy display name="Example: using Select" height="200px"
<App>
<Select>
<Option value="opt1" label="first"/>
<Option value="opt2" label="second"/>
<Option value="opt3" label="third"/>
</Select>
</App>
```
You can use `Select` with dynamic options:
```xmlui-pg copy display name="Example: using Select with dynamic options" height="200px"
<App>
<Select>
<Items data="{['one', 'two', 'three']}" >
<Option value="{$itemIndex}" label="{$item}" />
</Items>
</Select>
</App>
```
%-DESC-END
%-PROP-START initialValue
```xmlui-pg copy display name="Example: initialValue" height="200px"
<App>
<Select initialValue="opt3">
<Option value="opt1" label="first"/>
<Option value="opt2" label="second"/>
<Option value="opt3" label="third"/>
</Select>
</App>
```
%-PROP-END
%-PROP-START optionTemplate
```xmlui-pg copy display name="Example: optionTemplate" height="200px"
<App>
<Select>
<property name="optionTemplate">
<HStack verticalAlignment="center" gap="$space-0_5">
<Icon name="info" />
<Text value="{$item.label}" variant="strong" />
</HStack>
</property>
<Option value="opt1" label="first"/>
<Option value="opt2" label="second"/>
<Option value="opt3" label="third"/>
</Select>
</App>
```
%-PROP-END
%-PROP-START placeholder
```xmlui-pg copy display name="Example: placeholder" height="200px"
<App>
<Select placeholder="Please select an item">
<Option value="opt1" label="first"/>
<Option value="opt2" label="second"/>
<Option value="opt3" label="third"/>
</Select>
</App>
```
%-PROP-END
%-PROP-START validationStatus
```xmlui-pg copy display name="Example: validationStatus" height="280px"
<App>
<Select />
<Select validationStatus="valid" />
<Select validationStatus="warning" />
<Select validationStatus="error" />
</App>
```
%-PROP-END
%-PROP-START enabled
```xmlui-pg copy display name="Example: enabled"
<App>
<Select enabled="false" />
</App>
```
%-PROP-END
%-PROP-START emptyListTemplate
Click on the second field to see the custom empty list indicator.
```xmlui-pg copy {9-11} display name="Example: emptyListTemplate" height="260px"
<App>
<VStack>
<Text value="Default:" />
<Select />
</VStack>
<VStack>
<Text value="Custom:" />
<Select>
<property name="emptyListTemplate">
<Text variant="strong" value="Nothing to see here!" />
</property>
</Select>
</VStack>
</App>
```
%-PROP-END
%-PROP-START dropdownHeight
```xmlui-pg copy display name="Example: dropdownHeight" height="300px"
<App>
<Select dropdownHeight="180px">
<Option value="opt1" label="first"/>
<Option value="opt2" label="second"/>
<Option value="opt3" label="third"/>
<Option value="opt4" label="fourth"/>
<Option value="opt5" label="fifth"/>
<Option value="opt6" label="sixth"/>
<Option value="opt7" label="seventh"/>
<Option value="opt8" label="eighth"/>
<Option value="opt9" label="ninth"/>
<Option value="opt10" label="tenth"/>
<Option value="opt11" label="eleventh"/>
<Option value="opt12" label="twelfth"/>
</Select>
</App>
```
%-PROP-END
%-PROP-START multiSelect
```xmlui-pg copy display name="Example: multiSelect" height="300px"
<App>
<Select multiSelect="true" dropdownHeight="180px" >
<Option value="opt1" label="first"/>
<Option value="opt2" label="second"/>
<Option value="opt3" label="third"/>
<Option value="opt4" label="fourth"/>
<Option value="opt5" label="fifth"/>
<Option value="opt6" label="sixth"/>
<Option value="opt7" label="seventh"/>
<Option value="opt8" label="eighth"/>
<Option value="opt9" label="ninth"/>
<Option value="opt10" label="tenth"/>
<Option value="opt11" label="eleventh"/>
<Option value="opt12" label="twelfth"/>
</Select>
</App>
```
%-PROP-END
%-PROP-START optionLabelTemplate
In the template definition, you can use the `$item` context property to access the particular item's `label` and `value`.
```xmlui-pg copy {3-9} display name="Example: optionLabelTemplate" height="300px"
<App>
<Select initialValue="{0}" placeholder="Select..." searchable>
<property name="optionLabelTemplate">
<HStack
paddingHorizontal="$padding-tight"
border="2px dotted $color-primary-500">
<Text>{$item.label}</Text>
</HStack>
</property>
<Option value="{0}" label="zero"/>
<Option value="opt1" label="first"/>
<Option value="opt2" label="second"/>
<Option value="opt3" label="third"/>
</Select>
</App>
```
%-PROP-END
%-PROP-START valueTemplate
In the template definition, you can use the `$item` context property to access the particular item's `label` and `value`. The `$itemContext` property provides a `removeItem` method to delete a value from the current selection.
```xmlui-pg copy {3-15} display name="Example: valueTemplate" height="300px"
<App>
<Select initialValue="{0}" placeholder="Select..." multiSelect>
<property name="valueTemplate">
<HStack
paddingLeft="$padding-tight"
border="2px dotted $color-primary-500"
verticalAlignment="center">
<Text>{$item.label}</Text>
<Button
variant="ghost"
icon="close"
size="xs"
onClick="$itemContext.removeItem()"/>
</HStack>
</property>
<Option value="{0}" label="zero"/>
<Option value="opt1" label="first"/>
<Option value="opt2" label="second"/>
<Option value="opt3" label="third"/>
</Select>
</App>
```
%-PROP-END
%-EVENT-START didChange
```xmlui-pg copy display name="Example: didChange" height="260px"
<App>
<variable name="newValue" value="(none)" />
<Text value="{newValue}" />
<Select onDidChange="(newItem) => newValue = newItem">
<Option value="opt1" label="first"/>
<Option value="opt2" label="second"/>
<Option value="opt3" label="third"/>
</Select>
</App>
```
%-EVENT-END
%-EVENT-START gotFocus
```xmlui-pg copy {5-6} display name="Example: gotFocus/lostFocus" height="260px"
<App>
<variable name="isFocused" value="{false}" />
<Text value="Input control is focused: {isFocused}" />
<Select
onGotFocus="isFocused = true"
onLostFocus="isFocused = false">
<Option value="opt1" label="first"/>
<Option value="opt2" label="second"/>
<Option value="opt3" label="third"/>
</Select>
</App>
```
%-EVENT-END
%-API-START focus
```xmlui-pg copy display name="Example: focus()" height="260px"
<App>
<Button label="Focus Input" onClick="inputControl.focus()" />
<Select id="inputControl">
<Option value="opt1" label="first"/>
<Option value="opt2" label="second"/>
<Option value="opt3" label="third"/>
</Select>
</App>
```
%-API-END
%-API-START setValue
```xmlui-pg copy display name="Example: setValue()" height="260px"
<App>
<Select id="inputControl">
<Option value="opt1" label="first"/>
<Option value="opt2" label="second"/>
<Option value="opt3" label="third"/>
</Select>
<HStack>
<Button
label="Select 2nd Item"
onClick="inputControl.setValue('opt2')" />
<Button
label="Remove Selection"
onClick="inputControl.setValue('')" />
</HStack>
</App>
```
%-API-END
```
--------------------------------------------------------------------------------
/xmlui/src/components-core/utils/statementUtils.ts:
--------------------------------------------------------------------------------
```typescript
import {
T_ARROW_EXPRESSION,
T_ARROW_EXPRESSION_STATEMENT,
T_BLOCK_STATEMENT,
T_CALCULATED_MEMBER_ACCESS_EXPRESSION,
T_EMPTY_STATEMENT,
T_EXPRESSION_STATEMENT,
T_FUNCTION_INVOCATION_EXPRESSION,
T_IDENTIFIER,
T_LITERAL,
T_MEMBER_ACCESS_EXPRESSION,
T_RETURN_STATEMENT,
type ArrowExpression,
type ArrowExpressionStatement,
type BlockStatement,
type EmptyStatement,
type Expression,
type ExpressionStatement,
type FunctionInvocationExpression,
type Identifier,
type Statement,
} from "../script-runner/ScriptingSourceTree";
import type { QueueInfo } from "../script-runner/statement-queue";
import type { BindingTreeEvaluationContext } from "../script-runner/BindingTreeEvaluationContext";
import type { LogicalThread } from "../../abstractions/scripting/LogicalThread";
import { Parser } from "../../parsers/scripting/Parser";
import { processStatementQueueAsync } from "../script-runner/process-statement-async";
import { reportEngineError } from "../reportEngineError";
import { ScriptParseError } from "../EngineError";
/**
* Parse the specified source code as event handler code
* @param source Event handler source code
*/
export function parseHandlerCode(source: string): Statement[] {
// --- Parse the event code
const wParser = new Parser(source);
let parsedStatements: Statement[] | null = null;
try {
// --- Invoke the parser
parsedStatements = wParser.parseStatements();
} catch (err) {
// --- Parsing error with explicit error code
if (wParser.errors.length > 0) {
const err = wParser.errors[0];
reportEngineError(
new ScriptParseError(
`${err.code}(${err.line}, ${err.column}): ${wParser.errors[0].text}`,
source,
err.position,
),
);
} else {
throw err;
}
}
// --- Check for the completeness of source code parsing
if (!wParser.isEof) {
const tail = wParser.getTail();
reportEngineError(
new ScriptParseError(`Invalid tail found`, source, source.length - tail.length + 1),
);
}
// --- Done
return parsedStatements!;
}
/**
* Optionally transform statements in an event handler to an arrow expression statement
* @param stmts Statements to transform
* @param evalContext Optional event arguments
*/
export function prepareHandlerStatements(
stmts: Statement[],
evalContext?: BindingTreeEvaluationContext,
): Statement[] {
const stmtLength = stmts?.length ?? 0;
if (stmtLength === 0) {
// -- Use a no-op arrow function
return [
{
type: T_ARROW_EXPRESSION_STATEMENT,
expr: {
type: T_ARROW_EXPRESSION,
args: [],
statement: {
type: T_EMPTY_STATEMENT,
} as EmptyStatement,
} as ArrowExpression,
} as ArrowExpressionStatement,
];
}
if (stmtLength === 1) {
const stmt = stmts[0];
if (stmt.type === T_EXPRESSION_STATEMENT) {
// --- Handle single expression statements
if (evalContext) {
// --- We have a context in which the event handler is executed
if (stmt.expr.type === T_IDENTIFIER) {
// --- A single identifier, it is supposed to be an arrow function
// --- Use this arrow expression
return [convertExpressionToFunctionInvocation(stmt.expr)];
}
if (isMemberExpressionChain(stmt.expr)) {
// --- A single member expression chain, it is supposed to be an arrow function
// --- Use this arrow expression
return [convertExpressionToFunctionInvocation(stmt.expr)];
}
}
if (stmt.expr.type === T_ARROW_EXPRESSION) {
// --- A single arrow expression
return [
{
type: T_ARROW_EXPRESSION_STATEMENT,
expr: stmt.expr,
} as ArrowExpressionStatement,
];
}
// --- A single statement, turn into an arrow expression
return [
{
type: T_ARROW_EXPRESSION_STATEMENT,
expr: {
type: T_ARROW_EXPRESSION,
args: [],
statement: stmts[0],
} as ArrowExpression,
} as ArrowExpressionStatement,
];
}
if (stmt.type === T_RETURN_STATEMENT) {
// --- A single arrow expression with a return
return [
{
type: T_ARROW_EXPRESSION_STATEMENT,
expr: {
type: T_ARROW_EXPRESSION,
args: [],
statement: {
type: T_BLOCK_STATEMENT,
stmts: [stmt],
} as BlockStatement,
} as ArrowExpression,
} as ArrowExpressionStatement,
];
}
if (stmt.type === T_BLOCK_STATEMENT) {
// --- A single block statement?
if (
stmt.stmts[0].type === T_EXPRESSION_STATEMENT &&
stmt.stmts[0].expr.type === T_ARROW_EXPRESSION
) {
// --- A single block statement with a single arrow expression?
return [
{
type: T_ARROW_EXPRESSION_STATEMENT,
expr: stmt.stmts[0].expr,
} as ArrowExpressionStatement,
];
} else {
// --- Consider as a body of a no-arg arrow function
return [
{
type: T_ARROW_EXPRESSION_STATEMENT,
expr: {
type: T_ARROW_EXPRESSION,
args: [],
statement: stmts[0],
} as unknown as ArrowExpression,
} as ArrowExpressionStatement,
];
}
}
}
if (stmtLength > 1) {
// --- Use the statements as the body of a no-arg arrow function
return [
{
type: T_ARROW_EXPRESSION_STATEMENT,
expr: {
type: T_ARROW_EXPRESSION,
args: [],
statement: {
type: T_BLOCK_STATEMENT,
stmts,
} as BlockStatement,
} as unknown as ArrowExpression,
} as ArrowExpressionStatement,
];
}
// --- Nothing to transform
return stmts;
function isMemberExpressionChain(expr: Expression): boolean {
return (
(expr.type === T_MEMBER_ACCESS_EXPRESSION ||
(expr.type === T_CALCULATED_MEMBER_ACCESS_EXPRESSION && expr.member.type === T_LITERAL)) &&
(isMemberExpressionChain(expr.obj) || expr.obj.type === T_IDENTIFIER)
);
}
function convertExpressionToFunctionInvocation(expr: Expression): ArrowExpressionStatement {
// --- A single identifier, it is supposed to be an arrow function
// --- Create formal arguments
const formalArgs = evalContext.eventArgs
? evalContext.eventArgs.map(
(_, idx) =>
({
type: T_IDENTIFIER,
name: `__arg@@#__${idx}__`,
}) as Identifier,
)
: [];
// --- Add formal argument with current values
if (evalContext.eventArgs) {
evalContext.eventArgs.forEach((val, idx) => {
evalContext.localContext[`__arg@@#__${idx}__`] = val;
});
}
// --- Create the arrow expression
const arrowExpr: ArrowExpression = {
type: T_ARROW_EXPRESSION,
args: formalArgs,
statement: {
type: T_EXPRESSION_STATEMENT,
expr: {
type: T_FUNCTION_INVOCATION_EXPRESSION,
obj: expr,
arguments: [...formalArgs],
} as unknown as FunctionInvocationExpression,
} as ExpressionStatement,
} as ArrowExpression;
// --- Use this arrow expression
return {
type: T_ARROW_EXPRESSION_STATEMENT,
expr: arrowExpr,
} as ArrowExpressionStatement;
}
}
/**
* Runs the specified event handler code
* @param source Event handler source code
* @param evalContext Evaluation context to use
* @param thread Logical thread to use
* @param onStatementCompleted Callback for statement completion
*/
export async function runEventHandlerCode(
source: string,
evalContext: BindingTreeEvaluationContext,
thread?: LogicalThread,
): Promise<QueueInfo> {
const statements = prepareHandlerStatements(parseHandlerCode(source));
return await processStatementQueueAsync(statements, evalContext, thread);
}
```
--------------------------------------------------------------------------------
/xmlui/src/components-core/rendering/valueExtractor.ts:
--------------------------------------------------------------------------------
```typescript
import type { MutableRefObject } from "react";
import memoizeOne from "memoize-one";
import { isPlainObject, isString } from "lodash-es";
import type { AppContextObject } from "../../abstractions/AppContextDefs";
import type { MemoedVars } from "../abstractions/ComponentRenderer";
import { parseParameterString } from "../script-runner/ParameterParser";
import type { ComponentApi, ContainerState } from "../rendering/ContainerWrapper";
import { isPrimitive, pickFromObject, shallowCompare } from "../utils/misc";
import { collectVariableDependencies } from "../script-runner/visitors";
import { extractParam } from "../utils/extractParam";
import { StyleParser, toCssVar } from "../../parsers/style-parser/StyleParser";
import type { ValueExtractor } from "../../abstractions/RendererDefs";
function parseStringArray(input: string): string[] {
const trimmedInput = input.trim();
if (trimmedInput.startsWith("[") && trimmedInput.endsWith("]")) {
const content = trimmedInput.slice(1, -1);
const items = content.split(",").map((item) => item.trim());
return items.map((item) => item.replace(/^['"]|['"]$/g, ""));
} else {
throw new Error("Invalid array format");
}
}
function collectParams(expression: any) {
const params = [];
if (typeof expression === "string") {
params.push(...parseParameterString(expression));
} else if (Array.isArray(expression)) {
expression.forEach((exp) => {
params.push(...collectParams(exp));
});
} else if (isPlainObject(expression)) {
Object.entries(expression).forEach(([key, value]) => {
params.push(...collectParams(value));
});
}
return params;
}
export function asOptionalBoolean(value: any, defValue?: boolean | undefined) {
if (value === undefined || value === null) return defValue;
// Empty array returns false
if (Array.isArray(value) && value.length === 0) {
return false;
}
// Empty object returns false
if (typeof value === "object" && value !== null && !Array.isArray(value) && Object.keys(value).length === 0) {
return false;
}
if (typeof value === "number") {
return value !== 0;
}
if (typeof value === "string") {
value = value.trim().toLowerCase();
if (value === "") {
return false;
}
if (value === "true") {
return true;
}
if (value === "false") {
return false;
}
if (value !== "") {
return true;
}
}
if (typeof value === "boolean") {
return value;
}
return true;
}
// This function represents the extractor function we pass to extractValue
export function createValueExtractor(
state: ContainerState,
appContext: AppContextObject | undefined,
referenceTrackedApi: Record<string, ComponentApi>,
memoedVarsRef: MutableRefObject<MemoedVars>
): ValueExtractor {
// --- Extract the parameter and retrieve as is is
const extractor = (expression?: any, strict?: boolean): any => {
if (!expression) {
return expression;
}
if (isPrimitive(expression) && !isString(expression)) {
return expression;
}
let expressionString = expression;
if (typeof expression !== "string") {
if (strict) {
return expression;
}
expressionString = JSON.stringify(expression);
}
if (!memoedVarsRef.current.has(expressionString)) {
const params = collectParams(expression);
memoedVarsRef.current.set(expressionString, {
getDependencies: memoizeOne((_expressionString, referenceTrackedApi) => {
let ret = new Set<string>();
params.forEach((param) => {
if (param.type === "expression") {
ret = new Set([...ret, ...collectVariableDependencies(param.value, referenceTrackedApi)]);
}
});
return Array.from(ret);
}),
obtainValue: memoizeOne(
(expression, state, appContext, strict, deps, appContextDeps) => {
// console.log("COMP, BUST, obtain value called with", expression, state, appContext, deps, appContextDeps);
return extractParam(state, expression, appContext, strict);
},
(
[_newExpression, _newState, _newAppContext, _newStrict, newDeps, newAppContextDeps],
[_lastExpression, _lastState, _lastAppContext, _lastStrict, lastDeps, lastAppContextDeps]
) => {
return shallowCompare(newDeps, lastDeps) && shallowCompare(newAppContextDeps, lastAppContextDeps);
}
),
});
}
const expressionDependencies = memoedVarsRef.current
.get(expressionString)!
.getDependencies(expressionString, referenceTrackedApi);
const depValues = pickFromObject(state, expressionDependencies);
const appContextDepValues = pickFromObject(appContext, expressionDependencies);
// console.log("COMP, obtain value called with", depValues, appContextDepValues, expressionDependencies);
return memoedVarsRef.current.get(expressionString)!.obtainValue!(
expression,
state,
appContext,
strict,
depValues,
appContextDepValues
);
};
// --- Extract a string value
extractor.asString = (expression?: any) => {
return extractor(expression)?.toString() ?? "";
};
// --- Extract an optional string value
extractor.asOptionalString = <T extends string>(expression?: any, defValue?: string) => {
const value = extractor(expression)?.toString();
if (value === undefined || value === null) return defValue;
return value as T;
};
extractor.asDisplayText = (expression?: any) => {
let text = extractor(expression)?.toString();
if (text) {
let replaced = "";
let spaceFound = false;
for (const char of text) {
if (char === " " || char === "\t") {
replaced += spaceFound ? "\xa0" : " ";
spaceFound = true;
} else {
replaced += char;
spaceFound = char === "\xa0";
}
}
text = replaced;
}
return text;
};
// ---Extract an array of strings
extractor.asOptionalStringArray = (expression?: any) => {
const value = extractor(expression);
if (value === undefined || value === null) return [];
if (typeof value === "string" && value.trim() !== "") {
//console.log(parseStringArray(value));
return parseStringArray(value);
}
if (Array.isArray(value)) {
return value.map((item) => item.toString());
}
throw new Error(`An array of strings expected but ${typeof value} received.`);
};
// --- Extract a numeric value
extractor.asNumber = (expression?: any) => {
const value = extractor(expression);
if (typeof value === "number") return value;
throw new Error(`A numeric value expected but ${typeof value} received.`);
};
// --- Extract an optional numeric value
extractor.asOptionalNumber = (expression?: any, defValue?: number) => {
const value = extractor(expression);
if (value === undefined || value === null) return defValue;
if (typeof value === "string" && !isNaN(parseFloat(value))) {
return Number(value);
}
if (typeof value === "number") return value;
throw new Error(`A numeric value expected but ${typeof value} received.`);
};
// --- Extract a Boolean value
extractor.asBoolean = (expression?: any) => {
return !!extractor(expression);
};
// --- Extract an optional Boolean value
extractor.asOptionalBoolean = (expression?: any, defValue?: boolean) => {
return asOptionalBoolean(extractor(expression), defValue);
};
// --- Extract an optional size value
extractor.asSize = (expression?: any) => {
const value = extractor(expression);
if (value === undefined || value === null) return undefined;
try {
const parser = new StyleParser(value);
const size = parser.parseSize();
if (size?.themeId) {
return toCssVar(size.themeId);
}
return size ? `${size.value}${size.unit ?? "px"}` : undefined;
} catch {
return undefined;
}
};
// --- Done.
return extractor as ValueExtractor;
}
```
--------------------------------------------------------------------------------
/xmlui/src/components/Carousel/Carousel.md:
--------------------------------------------------------------------------------
```markdown
%-DESC-START
`Carousel` displays a slideshow by cycling through elements (images, text, or custom slides) in a carousel format. It provides an interactive way to present multiple content items in a single interface area with smooth transitions and navigation controls.
**Key features:**
- **Multiple orientations**: Supports both horizontal and vertical scrolling
- **Navigation controls**: Built-in previous/next buttons with customizable icons
- **Indicators**: Visual dots showing current position and allowing direct navigation
- **Autoplay functionality**: Automatic slide progression with configurable intervals
- **Loop support**: Continuous cycling through slides
- **Keyboard navigation**: Arrow key support for accessibility
- **Exposed methods**: Programmatic control via `scrollTo()`, `scrollNext()`, `scrollPrev()`, `canScrollNext()`, `canScrollPrev()`
%-DESC-END
%-PROP-START autoplay
This property indicates whether the carousel automatically scrolls through slides.
```xmlui-pg copy display name="Example: autoplay"
<App>
<Carousel autoplay autoplayInterval="2000" height="120px" loop>
<CarouselItem>
<Card title="Slide 1" />
</CarouselItem>
<CarouselItem>
<Card title="Slide 2" />
</CarouselItem>
<CarouselItem>
<Card title="Slide 3" />
</CarouselItem>
</Carousel>
</App>
```
%-PROP-END
%-PROP-START controls
This property indicates whether the carousel displays navigation controls (previous/next buttons).
```xmlui-pg copy display name="Example: controls"
<App>
<Carousel controls="true" height="120px">
<CarouselItem>Slide 1 with controls</CarouselItem>
<CarouselItem>Slide 2 with controls</CarouselItem>
</Carousel>
</App>
```
%-PROP-END
%-PROP-START indicators
This property indicates whether the carousel displays position indicators.
```xmlui-pg copy display name="Example: indicators"
<App>
<Carousel indicators="true" height="120px">
<CarouselItem>Slide 1 with indicators</CarouselItem>
<CarouselItem>Slide 2 with indicators</CarouselItem>
<CarouselItem>Slide 3 with indicators</CarouselItem>
</Carousel>
</App>
```
%-PROP-END
%-PROP-START loop
This property indicates whether the carousel loops continuously from the last slide back to the first.
```xmlui-pg copy display name="Example: loop"
<App>
<Carousel loop="true" height="120px">
<CarouselItem>
<Card title="First Slide" />
</CarouselItem>
<CarouselItem>
<Card title="Second Slide" />
</CarouselItem>
<CarouselItem>
<Card title="Third Slide" />
</CarouselItem>
</Carousel>
</App>
```
%-PROP-END
%-PROP-START orientation
This property indicates the orientation of the carousel. The `horizontal` value indicates that the carousel moves horizontally, and the `vertical` value indicates that the carousel moves vertically.
Available values:
| Value | Description |
| --- | --- |
| `horizontal` | The carousel moves horizontally **(default)** |
| `vertical` | The carousel moves vertically |
```xmlui-pg copy display name="Example: orientation"
<App>
<Carousel orientation="horizontal" height="120px">
<CarouselItem>Horizontal Slide 1</CarouselItem>
<CarouselItem>Horizontal Slide 2</CarouselItem>
</Carousel>
</App>
```
%-PROP-END
%-PROP-START startIndex
This property indicates the index of the first slide to display when the carousel initializes.
```xmlui-pg copy display name="Example: startIndex"
<App>
<Carousel startIndex="2" height="120px">
<CarouselItem>Slide 1</CarouselItem>
<CarouselItem>Slide 2</CarouselItem>
<CarouselItem>Slide 3 (starts here)</CarouselItem>
<CarouselItem>Slide 4</CarouselItem>
</Carousel>
</App>
```
%-PROP-END
%-PROP-START nextIcon
This property specifies the icon to display for the next control button.
```xmlui-pg copy display name="Example: custom icons"
<App>
<Carousel nextIcon="chevronright" prevIcon="chevronleft" height="120px">
<CarouselItem>Slide 1</CarouselItem>
<CarouselItem>Slide 2</CarouselItem>
<CarouselItem>Slide 3</CarouselItem>
</Carousel>
</App>
```
%-PROP-END
%-PROP-START prevIcon
This property specifies the icon to display for the previous control button.
%-PROP-END
%-PROP-START autoplayInterval
This property specifies the interval between autoplay transitions in milliseconds.
%-PROP-END
%-PROP-START stopAutoplayOnInteraction
This property indicates whether autoplay stops when the user interacts with the carousel (clicking controls, indicators, or using keyboard navigation).
%-PROP-END
%-PROP-START transitionDuration
This property indicates the duration of the transition between slides in milliseconds.
%-PROP-END
%-EVENT-START displayDidChange
This event is triggered when the active slide changes.
The event handler receives the active slide index as a parameter.
```xmlui-pg copy display name="Example: displayDidChange"
<App var.currentSlide="0">
<Carousel onDisplayDidChange="(index) => currentSlide = index" height="120px">
<CarouselItem>Slide 1</CarouselItem>
<CarouselItem>Slide 2</CarouselItem>
<CarouselItem>Slide 3</CarouselItem>
</Carousel>
<Text>Current slide: {currentSlide + 1}</Text>
</App>
```
%-EVENT-END
%-STYLE-START
### Theme Variables
| Variable | Default Value (Light) | Default Value (Dark) |
| --- | --- | --- |
| [backgroundColor](../styles-and-themes/common-units/#color)-control-Carousel | $color-primary | $color-primary |
| [backgroundColor](../styles-and-themes/common-units/#color)-control-active-Carousel | $color-primary | $color-primary |
| [backgroundColor](../styles-and-themes/common-units/#color)-control-disabled-Carousel | $color-surface-200 | $color-surface-200 |
| [backgroundColor](../styles-and-themes/common-units/#color)-control-hover-Carousel | $color-primary | $color-primary |
| [backgroundColor](../styles-and-themes/common-units/#color)-indicator-Carousel | $color-surface-200 | $color-surface-200 |
| [backgroundColor](../styles-and-themes/common-units/#color)-indicator-active-Carousel | $color-primary | $color-primary |
| [backgroundColor](../styles-and-themes/common-units/#color)-indicator-hover-Carousel | $color-surface-200 | $color-surface-200 |
| [borderRadius](../styles-and-themes/common-units/#border-rounding)-control-Carousel | 50% | 50% |
| [height](../styles-and-themes/common-units/#size)-Carousel | 100% | 100% |
| [height](../styles-and-themes/common-units/#size)-control-Carousel | 36px | 36px |
| [height](../styles-and-themes/common-units/#size)-indicator-Carousel | 6px | 6px |
| [textColor](../styles-and-themes/common-units/#color)-control-Carousel | $textColor | $textColor |
| [textColor](../styles-and-themes/common-units/#color)-control-active-Carousel | $color-primary | $color-primary |
| [textColor](../styles-and-themes/common-units/#color)-control-disabled-Carousel | $textColor-disabled | $textColor-disabled |
| [textColor](../styles-and-themes/common-units/#color)-control-hover-Carousel | $textColor | $textColor |
| [textColor](../styles-and-themes/common-units/#color)-indicator-Carousel | $color-primary | $color-primary |
| [textColor](../styles-and-themes/common-units/#color)-indicator-active-Carousel | $color-primary | $color-primary |
| [textColor](../styles-and-themes/common-units/#color)-indicator-hover-Carousel | $color-primary | $color-primary |
| [width](../styles-and-themes/common-units/#size)-Carousel | 100% | 100% |
| [width](../styles-and-themes/common-units/#size)-control-Carousel | 36px | 36px |
| [width](../styles-and-themes/common-units/#size)-indicator-Carousel | 25px | 25px |
### Navigation Controls
The carousel provides built-in navigation controls that can be customized through theme variables:
```xmlui-pg copy name="Example: Custom control styling"
<App>
<Theme
backgroundColor-control-Carousel="red"
textColor-control-Carousel="white"
borderRadius-control-Carousel="4px">
<Carousel height="120px">
<CarouselItem>Slide 1</CarouselItem>
<CarouselItem>Slide 2</CarouselItem>
<CarouselItem>Slide 3</CarouselItem>
</Carousel>
</Theme>
</App>
```
%-STYLE-END
```
--------------------------------------------------------------------------------
/xmlui/src/components/_conventions.md:
--------------------------------------------------------------------------------
```markdown
# XMLUI Component Conventions
This document outlines the conventions and patterns used in the XMLUI component system, based on analysis of components like Avatar, Button, and Card.
## Component Structure
### Dual-File Pattern
Components are structured using a dual-file pattern:
- **Native Component** (`*Native.tsx`)
- Pure React implementation
- Uses `forwardRef` pattern
- Contains the actual rendering logic
- Defines prop types and default props
- **Renderer Component** (`.tsx`)
- Provides metadata using `createMetadata`
- Registers component with `createComponentRenderer`
- Defines theme variables
- Maps XMLUI props to native component props
Example relationship:
```typescript
// Avatar.tsx (Renderer)
export const avatarComponentRenderer = createComponentRenderer(
"Avatar",
AvatarMd,
({ node, extractValue, lookupEventHandler, layoutCss, extractResourceUrl }) => {
return (
<Avatar
size={node.props?.size}
url={extractResourceUrl(node.props.url)}
name={extractValue(node.props.name)}
style={layoutCss}
onClick={lookupEventHandler("click")}
/>
);
},
);
// AvatarNative.tsx (Implementation)
export const Avatar = forwardRef(function Avatar(
{ size = defaultProps.size, url, name, style, onClick, ...rest }: Props,
ref: Ref<any>,
) {
// Implementation details...
});
```
### Single-File Pattern Variation
Some XMLUI components are implemented using a single-file pattern:
- **Combined Component** (`.tsx`)
- Contains both the React implementation and XMLUI renderer in a single file
- Defines metadata using `createMetadata`
- Registers component with `createComponentRenderer`
- Does not separate the implementation into a `*Native.tsx` file
- Often used for simpler components or those that primarily compose other native components
Example: `ToneChangerButton.tsx` combines the implementation and renderer in one file:
```typescript
export function ToneChangerButton({
lightToDarkIcon = defaultProps.lightToDarkIcon,
darkToLightIcon = defaultProps.darkToLightIcon,
}) {
// Implementation...
}
export const toneChangerButtonComponentRenderer = createComponentRenderer(
COMP,
ToneChangerButtonMd,
({ node, extractValue }) => {
return (
<ToneChangerButton
lightToDarkIcon={extractValue.asOptionalString(node.props.lightToDarkIcon)}
darkToLightIcon={extractValue.asOptionalString(node.props.darkToLightIcon)}
/>
);
},
);
```
This variation is still a fully functional XMLUI component and is registered in the ComponentProvider, making it available in XMLUI markup.
## Component API and State
### API Registration
Components expose methods through a standardized API registration pattern:
```typescript
useEffect(() => {
registerComponentApi?.({
setValue,
focus,
});
}, [registerComponentApi, setValue, focus]);
```
### State Synchronization
Components keep their internal state synchronized with XMLUI:
```typescript
// Update component state and notify XMLUI
const setValue = useEvent((newValue) => {
setInternalState(newValue);
updateState?.({ value: newValue });
});
// Sync state on initial render or changes
useEffect(() => {
updateState?.({ value: currentState });
}, [updateState, currentState]);
```
## Event Handling
Events are:
1. Declared in component metadata
2. Implemented in the native component
3. Connected via `lookupEventHandler` in the renderer
Common events:
- `click` / `onClick`
- `didChange` (value changes)
- `gotFocus` / `lostFocus`
- Component-specific events (e.g., `onReset`)
## Styling and Theming
### SCSS Module Pattern
- Each component has its own SCSS module (e.g., `Avatar.module.scss`)
- Theme variables follow naming convention: `propertyName-ComponentName`
- Variables are exported and parsed with `parseScssVar`
- CSS classes are composed with the `classnames` library
### Theme Variables
Theme variables are defined in the component metadata:
```typescript
defaultThemeVars: {
[`borderRadius-${COMP}`]: "4px",
[`boxShadow-${COMP}`]: "inset 0 0 0 1px rgba(4,32,69,0.1)",
[`textColor-${COMP}`]: "$textColor-secondary",
// ...other variables
}
```
Components can be styled using the theme system or through direct props.
## Component Registration
Components are registered with the `ComponentRegistry` during framework initialization:
```typescript
if (process.env.VITE_USED_COMPONENTS_Avatar !== "false") {
this.registerCoreComponent(avatarComponentRenderer);
}
```
This allows conditional inclusion of components and provides centralized component management.
## Testing
Components use a driver pattern for testing:
```typescript
// Each component has a dedicated driver class
export class AvatarDriver extends ComponentDriver {
// Driver-specific methods
}
// Used in tests
test("can render 2 initials", async ({ initTestBed, createAvatarDriver }) => {
await initTestBed(`<Avatar name="Tim Smith"/>`);
await expect((await createAvatarDriver()).component).toContainText("TS");
});
```
## Reusable Component System
XMLUI supports user-defined components using the `<Component>` tag:
```xml
<Component name="InfoCard">
<Card width="{$props.width}" borderRadius="8px" boxShadow="$boxShadow-spread">
<Text>{$props.title}</Text>
<Text fontWeight="$fontWeight-extra-bold" fontSize="larger">
{ $props.currency === 'true' ? '$' + $props.value : $props.value }
</Text>
</Card>
</Component>
```
Components can access properties via `$props` context variable, and expose methods through the API registration mechanism.
## Layout Integration
Components behave differently depending on their parent layout containers:
- In `Stack` layouts, component layout properties might be ignored
- In `FlowLayout`, components get wrapped in container elements
- Explicit layout containers within components help control arrangement
## Native-Only React Components
The following components in the XMLUI codebase are implemented as native React components only. These components don't have corresponding XMLUI renderers registered in the ComponentProvider and are not directly available in XMLUI markup. Instead, they serve as internal implementation details, UI utilities, or components used by other XMLUI components.
- `InspectButton`: A button component used for turning inspection mode on/off in XMLUI development environment
- `ProfileMenu`: A profile menu component used within other components but not exposed directly to XMLUI
- `Toggle`: A toggle component that provides base functionality that may be used by other components
- `VisuallyHidden`: A utility component that wraps Radix UI's VisuallyHidden for accessibility
- `SlotItem`: An internal component for implementing the slot mechanism
- `IconProvider`: Provides icon context for the icon system
- `IconRegistryContext`: Manages icon registry context
### Component Structure Observations
1. **Pure Native Components** tend to:
- Be located in a single file (no paired renderer)
- Have a `.tsx` extension (sometimes with a `.module.scss` file)
- Not include `createComponentRenderer` or `createMetadata` calls
- Often be used internally by other XMLUI components
2. **Helper Components**:
- Typically simple with a focused utility purpose
- May be wrappers around third-party components
- Often have minimal props and internal state
These native-only components demonstrate the architectural separation in XMLUI, where not every React component needs to be exposed to the XMLUI markup language. This separation enables internal implementation flexibility while maintaining a clean API surface for XMLUI users.
## Best Practices
1. Keep native component implementations pure and focused
2. Define clear metadata and documentation for components
3. Support proper theming through theme variables
4. Implement consistent API and event patterns
5. Ensure proper testing with component drivers
6. Support accessibility through ARIA attributes and keyboard navigation
7. Ensure proper reference forwarding through `forwardRef`
8. Provide sensible defaults for all props
```
--------------------------------------------------------------------------------
/blog/public/mockServiceWorker.js:
--------------------------------------------------------------------------------
```javascript
/* eslint-disable */
/* tslint:disable */
/**
* Mock Service Worker.
* @see https://github.com/mswjs/msw
* - Please do NOT modify this file.
* - Please do NOT serve this file on production.
*/
const PACKAGE_VERSION = '2.8.4'
const INTEGRITY_CHECKSUM = '00729d72e3b82faf54ca8b9621dbb96f'
const IS_MOCKED_RESPONSE = Symbol('isMockedResponse')
const activeClientIds = new Set()
self.addEventListener('install', function () {
self.skipWaiting()
})
self.addEventListener('activate', function (event) {
event.waitUntil(self.clients.claim())
})
self.addEventListener('message', async function (event) {
const clientId = event.source.id
if (!clientId || !self.clients) {
return
}
const client = await self.clients.get(clientId)
if (!client) {
return
}
const allClients = await self.clients.matchAll({
type: 'window',
})
switch (event.data) {
case 'KEEPALIVE_REQUEST': {
sendToClient(client, {
type: 'KEEPALIVE_RESPONSE',
})
break
}
case 'INTEGRITY_CHECK_REQUEST': {
sendToClient(client, {
type: 'INTEGRITY_CHECK_RESPONSE',
payload: {
packageVersion: PACKAGE_VERSION,
checksum: INTEGRITY_CHECKSUM,
},
})
break
}
case 'MOCK_ACTIVATE': {
activeClientIds.add(clientId)
sendToClient(client, {
type: 'MOCKING_ENABLED',
payload: {
client: {
id: client.id,
frameType: client.frameType,
},
},
})
break
}
case 'MOCK_DEACTIVATE': {
activeClientIds.delete(clientId)
break
}
case 'CLIENT_CLOSED': {
activeClientIds.delete(clientId)
const remainingClients = allClients.filter((client) => {
return client.id !== clientId
})
// Unregister itself when there are no more clients
if (remainingClients.length === 0) {
self.registration.unregister()
}
break
}
}
})
self.addEventListener('fetch', function (event) {
const { request } = event
// Bypass navigation requests.
if (request.mode === 'navigate') {
return
}
// Opening the DevTools triggers the "only-if-cached" request
// that cannot be handled by the worker. Bypass such requests.
if (request.cache === 'only-if-cached' && request.mode !== 'same-origin') {
return
}
// Bypass all requests when there are no active clients.
// Prevents the self-unregistered worked from handling requests
// after it's been deleted (still remains active until the next reload).
if (activeClientIds.size === 0) {
return
}
// Generate unique request ID.
const requestId = crypto.randomUUID()
event.respondWith(handleRequest(event, requestId))
})
async function handleRequest(event, requestId) {
const client = await resolveMainClient(event)
const response = await getResponse(event, client, requestId)
// Send back the response clone for the "response:*" life-cycle events.
// Ensure MSW is active and ready to handle the message, otherwise
// this message will pend indefinitely.
if (client && activeClientIds.has(client.id)) {
;(async function () {
const responseClone = response.clone()
sendToClient(
client,
{
type: 'RESPONSE',
payload: {
requestId,
isMockedResponse: IS_MOCKED_RESPONSE in response,
type: responseClone.type,
status: responseClone.status,
statusText: responseClone.statusText,
body: responseClone.body,
headers: Object.fromEntries(responseClone.headers.entries()),
},
},
[responseClone.body],
)
})()
}
return response
}
// Resolve the main client for the given event.
// Client that issues a request doesn't necessarily equal the client
// that registered the worker. It's with the latter the worker should
// communicate with during the response resolving phase.
async function resolveMainClient(event) {
const client = await self.clients.get(event.clientId)
if (activeClientIds.has(event.clientId)) {
return client
}
if (client?.frameType === 'top-level') {
return client
}
const allClients = await self.clients.matchAll({
type: 'window',
})
return allClients
.filter((client) => {
// Get only those clients that are currently visible.
return client.visibilityState === 'visible'
})
.find((client) => {
// Find the client ID that's recorded in the
// set of clients that have registered the worker.
return activeClientIds.has(client.id)
})
}
async function getResponse(event, client, requestId) {
const { request } = event
// Clone the request because it might've been already used
// (i.e. its body has been read and sent to the client).
const requestClone = request.clone()
function passthrough() {
// Cast the request headers to a new Headers instance
// so the headers can be manipulated with.
const headers = new Headers(requestClone.headers)
// Remove the "accept" header value that marked this request as passthrough.
// This prevents request alteration and also keeps it compliant with the
// user-defined CORS policies.
const acceptHeader = headers.get('accept')
if (acceptHeader) {
const values = acceptHeader.split(',').map((value) => value.trim())
const filteredValues = values.filter(
(value) => value !== 'msw/passthrough',
)
if (filteredValues.length > 0) {
headers.set('accept', filteredValues.join(', '))
} else {
headers.delete('accept')
}
}
return fetch(requestClone, { headers })
}
// Bypass mocking when the client is not active.
if (!client) {
return passthrough()
}
// Bypass initial page load requests (i.e. static assets).
// The absence of the immediate/parent client in the map of the active clients
// means that MSW hasn't dispatched the "MOCK_ACTIVATE" event yet
// and is not ready to handle requests.
if (!activeClientIds.has(client.id)) {
return passthrough()
}
// Notify the client that a request has been intercepted.
const requestBuffer = await request.arrayBuffer()
const clientMessage = await sendToClient(
client,
{
type: 'REQUEST',
payload: {
id: requestId,
url: request.url,
mode: request.mode,
method: request.method,
headers: Object.fromEntries(request.headers.entries()),
cache: request.cache,
credentials: request.credentials,
destination: request.destination,
integrity: request.integrity,
redirect: request.redirect,
referrer: request.referrer,
referrerPolicy: request.referrerPolicy,
body: requestBuffer,
keepalive: request.keepalive,
},
},
[requestBuffer],
)
switch (clientMessage.type) {
case 'MOCK_RESPONSE': {
return respondWithMock(clientMessage.data)
}
case 'PASSTHROUGH': {
return passthrough()
}
}
return passthrough()
}
function sendToClient(client, message, transferrables = []) {
return new Promise((resolve, reject) => {
const channel = new MessageChannel()
channel.port1.onmessage = (event) => {
if (event.data && event.data.error) {
return reject(event.data.error)
}
resolve(event.data)
}
client.postMessage(
message,
[channel.port2].concat(transferrables.filter(Boolean)),
)
})
}
async function respondWithMock(response) {
// Setting response status code to 0 is a no-op.
// However, when responding with a "Response.error()", the produced Response
// instance will have status code set to 0. Since it's not possible to create
// a Response instance with status code 0, handle that use-case separately.
if (response.status === 0) {
return Response.error()
}
const mockedResponse = new Response(response.body, response)
Reflect.defineProperty(mockedResponse, IS_MOCKED_RESPONSE, {
value: true,
enumerable: true,
})
return mockedResponse
}
```